diff options
Diffstat (limited to 'dom/media/platforms/agnostic/VorbisDecoder.cpp')
-rw-r--r-- | dom/media/platforms/agnostic/VorbisDecoder.cpp | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/VorbisDecoder.cpp b/dom/media/platforms/agnostic/VorbisDecoder.cpp new file mode 100644 index 0000000000..99b1a95931 --- /dev/null +++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp @@ -0,0 +1,364 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VorbisDecoder.h" + +#include "VideoUtils.h" +#include "VorbisUtils.h" +#include "XiphExtradata.h" +#include "mozilla/Logging.h" +#include "mozilla/PodOperations.h" +#include "mozilla/SyncRunnable.h" + +#undef LOG +#define LOG(type, msg) MOZ_LOG(sPDMLog, type, msg) + +namespace mozilla { + +ogg_packet InitVorbisPacket(const unsigned char* aData, size_t aLength, + bool aBOS, bool aEOS, int64_t aGranulepos, + int64_t aPacketNo) { + ogg_packet packet; + packet.packet = const_cast<unsigned char*>(aData); + packet.bytes = aLength; + packet.b_o_s = aBOS; + packet.e_o_s = aEOS; + packet.granulepos = aGranulepos; + packet.packetno = aPacketNo; + return packet; +} + +VorbisDataDecoder::VorbisDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()), mPacketCount(0), mFrames(0) { + // Zero these member vars to avoid crashes in Vorbis clear functions when + // destructor is called before |Init|. + PodZero(&mVorbisBlock); + PodZero(&mVorbisDsp); + PodZero(&mVorbisInfo); + PodZero(&mVorbisComment); +} + +VorbisDataDecoder::~VorbisDataDecoder() { + vorbis_block_clear(&mVorbisBlock); + vorbis_dsp_clear(&mVorbisDsp); + vorbis_info_clear(&mVorbisInfo); + vorbis_comment_clear(&mVorbisComment); +} + +RefPtr<ShutdownPromise> VorbisDataDecoder::Shutdown() { + // mThread may not be set if Init hasn't been called first. + MOZ_ASSERT(!mThread || mThread->IsOnCurrentThread()); + return ShutdownPromise::CreateAndResolve(true, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> VorbisDataDecoder::Init() { + mThread = GetCurrentSerialEventTarget(); + vorbis_info_init(&mVorbisInfo); + vorbis_comment_init(&mVorbisComment); + PodZero(&mVorbisDsp); + PodZero(&mVorbisBlock); + + AutoTArray<unsigned char*, 4> headers; + AutoTArray<size_t, 4> headerLens; + MOZ_ASSERT(mInfo.mCodecSpecificConfig.is<VorbisCodecSpecificData>(), + "Vorbis decoder should get vorbis codec specific data"); + RefPtr<MediaByteBuffer> vorbisHeaderBlob = + GetAudioCodecSpecificBlob(mInfo.mCodecSpecificConfig); + if (!XiphExtradataToHeaders(headers, headerLens, vorbisHeaderBlob->Elements(), + vorbisHeaderBlob->Length())) { + LOG(LogLevel::Warning, ("VorbisDecoder: could not get vorbis header")); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Could not get vorbis header.")), + __func__); + } + for (size_t i = 0; i < headers.Length(); i++) { + if (NS_FAILED(DecodeHeader(headers[i], headerLens[i]))) { + LOG(LogLevel::Warning, + ("VorbisDecoder: could not get decode vorbis header")); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Could not decode vorbis header.")), + __func__); + } + } + + MOZ_ASSERT(mPacketCount == 3); + + int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo); + if (r) { + LOG(LogLevel::Warning, ("VorbisDecoder: could not init vorbis decoder")); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Systhesis init fail.")), + __func__); + } + + r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock); + if (r) { + LOG(LogLevel::Warning, ("VorbisDecoder: could not init vorbis block")); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Block init fail.")), + __func__); + } + + if (mInfo.mRate != (uint32_t)mVorbisDsp.vi->rate) { + LOG(LogLevel::Warning, ("VorbisDecoder: Invalid Vorbis header: container " + "and codec rate do not match!")); + } + if (mInfo.mChannels != (uint32_t)mVorbisDsp.vi->channels) { + LOG(LogLevel::Warning, ("VorbisDecoder: Invalid Vorbis header: container " + "and codec channels do not match!")); + } + + AudioConfig::ChannelLayout layout(mVorbisDsp.vi->channels); + if (!layout.IsValid()) { + LOG(LogLevel::Warning, + ("VorbisDecoder: Invalid Vorbis header: invalid channel layout!")); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Invalid audio layout.")), + __func__); + } + + return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); +} + +nsresult VorbisDataDecoder::DecodeHeader(const unsigned char* aData, + size_t aLength) { + bool bos = mPacketCount == 0; + ogg_packet pkt = + InitVorbisPacket(aData, aLength, bos, false, 0, mPacketCount++); + MOZ_ASSERT(mPacketCount <= 3); + + int r = vorbis_synthesis_headerin(&mVorbisInfo, &mVorbisComment, &pkt); + return r == 0 ? NS_OK : NS_ERROR_FAILURE; +} + +RefPtr<MediaDataDecoder::DecodePromise> VorbisDataDecoder::Decode( + MediaRawData* aSample) { + MOZ_ASSERT(mThread->IsOnCurrentThread()); + PROCESS_DECODE_LOG(aSample); + + const unsigned char* aData = aSample->Data(); + size_t aLength = aSample->Size(); + int64_t aOffset = aSample->mOffset; + + MOZ_ASSERT(mPacketCount >= 3); + + if (!mLastFrameTime || + mLastFrameTime.ref() != aSample->mTime.ToMicroseconds()) { + // We are starting a new block. + mFrames = 0; + mLastFrameTime = Some(aSample->mTime.ToMicroseconds()); + } + + ogg_packet pkt = + InitVorbisPacket(aData, aLength, false, aSample->mEOS, + aSample->mTimecode.ToMicroseconds(), mPacketCount++); + + int err = vorbis_synthesis(&mVorbisBlock, &pkt); + if (err) { + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis:%d", err)), + __func__); + LOG(LogLevel::Warning, ("vorbis_synthesis returned an error")); + } + + err = vorbis_synthesis_blockin(&mVorbisDsp, &mVorbisBlock); + if (err) { + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis_blockin:%d", err)), + __func__); + LOG(LogLevel::Warning, ("vorbis_synthesis_blockin returned an error")); + } + + VorbisPCMValue** pcm = 0; + int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); + if (frames == 0) { + return DecodePromise::CreateAndResolve(DecodedData(), __func__); + } + + DecodedData results; + while (frames > 0) { + uint32_t channels = mVorbisDsp.vi->channels; + uint32_t rate = mVorbisDsp.vi->rate; + AlignedAudioBuffer buffer(frames * channels); + if (!buffer) { + LOG(LogLevel::Warning, ("VorbisDecoder: cannot allocate buffer")); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); + } + for (uint32_t j = 0; j < channels; ++j) { + VorbisPCMValue* channel = pcm[j]; + for (uint32_t i = 0; i < uint32_t(frames); ++i) { + buffer[i * channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]); + } + } + + auto duration = FramesToTimeUnit(frames, rate); + if (!duration.IsValid()) { + LOG(LogLevel::Warning, ("VorbisDecoder: invalid packet duration")); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow converting audio duration")), + __func__); + } + auto total_duration = FramesToTimeUnit(mFrames, rate); + if (!total_duration.IsValid()) { + LOG(LogLevel::Warning, ("VorbisDecoder: invalid total duration")); + return DecodePromise::CreateAndReject( + MediaResult( + NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow converting audio total_duration")), + __func__); + } + + auto time = total_duration + aSample->mTime; + if (!time.IsValid()) { + LOG(LogLevel::Warning, ("VorbisDecoder: invalid sample time")); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL( + "Overflow adding total_duration and aSample->mTime")), + __func__); + }; + + if (!mAudioConverter) { + const AudioConfig::ChannelLayout layout = + AudioConfig::ChannelLayout(channels, VorbisLayout(channels)); + AudioConfig in(layout, channels, rate); + AudioConfig out(AudioConfig::ChannelLayout::SMPTEDefault(layout), + channels, rate); + mAudioConverter = MakeUnique<AudioConverter>(in, out); + } + MOZ_ASSERT(mAudioConverter->CanWorkInPlace()); + AudioSampleBuffer data(std::move(buffer)); + data = mAudioConverter->Process(std::move(data)); + + RefPtr<AudioData> audio = + new AudioData(aOffset, time, data.Forget(), channels, rate, + mAudioConverter->OutputConfig().Layout().Map()); + MOZ_DIAGNOSTIC_ASSERT(duration == audio->mDuration, "must be equal"); + results.AppendElement(std::move(audio)); + mFrames += frames; + err = vorbis_synthesis_read(&mVorbisDsp, frames); + if (err) { + LOG(LogLevel::Warning, ("VorbisDecoder: vorbis_synthesis_read")); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis_read:%d", err)), + __func__); + } + + frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); + } + return DecodePromise::CreateAndResolve(std::move(results), __func__); +} + +RefPtr<MediaDataDecoder::DecodePromise> VorbisDataDecoder::Drain() { + MOZ_ASSERT(mThread->IsOnCurrentThread()); + return DecodePromise::CreateAndResolve(DecodedData(), __func__); +} + +RefPtr<MediaDataDecoder::FlushPromise> VorbisDataDecoder::Flush() { + MOZ_ASSERT(mThread->IsOnCurrentThread()); + // Ignore failed results from vorbis_synthesis_restart. They + // aren't fatal and it fails when ResetDecode is called at a + // time when no vorbis data has been read. + vorbis_synthesis_restart(&mVorbisDsp); + mLastFrameTime.reset(); + return FlushPromise::CreateAndResolve(true, __func__); +} + +/* static */ +bool VorbisDataDecoder::IsVorbis(const nsACString& aMimeType) { + return aMimeType.EqualsLiteral("audio/vorbis"); +} + +/* static */ +const AudioConfig::Channel* VorbisDataDecoder::VorbisLayout( + uint32_t aChannels) { + // From https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html + // Section 4.3.9. + typedef AudioConfig::Channel Channel; + + switch (aChannels) { + case 1: // the stream is monophonic + { + static const Channel config[] = {AudioConfig::CHANNEL_FRONT_CENTER}; + return config; + } + case 2: // the stream is stereo. channel order: left, right + { + static const Channel config[] = {AudioConfig::CHANNEL_FRONT_LEFT, + AudioConfig::CHANNEL_FRONT_RIGHT}; + return config; + } + case 3: // the stream is a 1d-surround encoding. channel order: left, + // center, right + { + static const Channel config[] = {AudioConfig::CHANNEL_FRONT_LEFT, + AudioConfig::CHANNEL_FRONT_CENTER, + AudioConfig::CHANNEL_FRONT_RIGHT}; + return config; + } + case 4: // the stream is quadraphonic surround. channel order: front left, + // front right, rear left, rear right + { + static const Channel config[] = { + AudioConfig::CHANNEL_FRONT_LEFT, AudioConfig::CHANNEL_FRONT_RIGHT, + AudioConfig::CHANNEL_BACK_LEFT, AudioConfig::CHANNEL_BACK_RIGHT}; + return config; + } + case 5: // the stream is five-channel surround. channel order: front left, + // center, front right, rear left, rear right + { + static const Channel config[] = { + AudioConfig::CHANNEL_FRONT_LEFT, AudioConfig::CHANNEL_FRONT_CENTER, + AudioConfig::CHANNEL_FRONT_RIGHT, AudioConfig::CHANNEL_BACK_LEFT, + AudioConfig::CHANNEL_BACK_RIGHT}; + return config; + } + case 6: // the stream is 5.1 surround. channel order: front left, center, + // front right, rear left, rear right, LFE + { + static const Channel config[] = { + AudioConfig::CHANNEL_FRONT_LEFT, AudioConfig::CHANNEL_FRONT_CENTER, + AudioConfig::CHANNEL_FRONT_RIGHT, AudioConfig::CHANNEL_BACK_LEFT, + AudioConfig::CHANNEL_BACK_RIGHT, AudioConfig::CHANNEL_LFE}; + return config; + } + case 7: // surround. channel order: front left, center, front right, side + // left, side right, rear center, LFE + { + static const Channel config[] = { + AudioConfig::CHANNEL_FRONT_LEFT, AudioConfig::CHANNEL_FRONT_CENTER, + AudioConfig::CHANNEL_FRONT_RIGHT, AudioConfig::CHANNEL_SIDE_LEFT, + AudioConfig::CHANNEL_SIDE_RIGHT, AudioConfig::CHANNEL_BACK_CENTER, + AudioConfig::CHANNEL_LFE}; + return config; + } + case 8: // the stream is 7.1 surround. channel order: front left, center, + // front right, side left, side right, rear left, rear right, LFE + { + static const Channel config[] = { + AudioConfig::CHANNEL_FRONT_LEFT, AudioConfig::CHANNEL_FRONT_CENTER, + AudioConfig::CHANNEL_FRONT_RIGHT, AudioConfig::CHANNEL_SIDE_LEFT, + AudioConfig::CHANNEL_SIDE_RIGHT, AudioConfig::CHANNEL_BACK_LEFT, + AudioConfig::CHANNEL_BACK_RIGHT, AudioConfig::CHANNEL_LFE}; + return config; + } + default: + return nullptr; + } +} + +} // namespace mozilla +#undef LOG |