michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "VorbisTrackEncoder.h" michael@0: #include michael@0: #include michael@0: #include "WebMWriter.h" michael@0: michael@0: // One actually used: Encoding using a VBR quality mode. The usable range is -.1 michael@0: // (lowest quality, smallest file) to 1. (highest quality, largest file). michael@0: // Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR michael@0: // ret = vorbis_encode_init_vbr(&vi,2,44100,.4); michael@0: static const float BASE_QUALITY = 0.4f; michael@0: michael@0: namespace mozilla { michael@0: michael@0: #undef LOG michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gVorbisTrackEncoderLog; michael@0: #define VORBISLOG(msg, ...) PR_LOG(gVorbisTrackEncoderLog, PR_LOG_DEBUG, \ michael@0: (msg, ##__VA_ARGS__)) michael@0: #else michael@0: #define VORBISLOG(msg, ...) michael@0: #endif michael@0: michael@0: VorbisTrackEncoder::VorbisTrackEncoder() michael@0: : AudioTrackEncoder() michael@0: { michael@0: MOZ_COUNT_CTOR(VorbisTrackEncoder); michael@0: #ifdef PR_LOGGING michael@0: if (!gVorbisTrackEncoderLog) { michael@0: gVorbisTrackEncoderLog = PR_NewLogModule("VorbisTrackEncoder"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: VorbisTrackEncoder::~VorbisTrackEncoder() michael@0: { michael@0: MOZ_COUNT_DTOR(VorbisTrackEncoder); michael@0: if (mInitialized) { michael@0: vorbis_block_clear(&mVorbisBlock); michael@0: vorbis_dsp_clear(&mVorbisDsp); michael@0: vorbis_info_clear(&mVorbisInfo); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: VorbisTrackEncoder::Init(int aChannels, int aSamplingRate) michael@0: { michael@0: if (aChannels <= 0 || aChannels > 8) { michael@0: VORBISLOG("aChannels <= 0 || aChannels > 8"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // This monitor is used to wake up other methods that are waiting for encoder michael@0: // to be completely initialized. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: mChannels = aChannels; michael@0: mSamplingRate = aSamplingRate; michael@0: michael@0: int ret = 0; michael@0: vorbis_info_init(&mVorbisInfo); michael@0: michael@0: ret = vorbis_encode_init_vbr(&mVorbisInfo, mChannels, mSamplingRate, michael@0: BASE_QUALITY); michael@0: michael@0: mInitialized = (ret == 0); michael@0: michael@0: if (mInitialized) { michael@0: // Set up the analysis state and auxiliary encoding storage michael@0: vorbis_analysis_init(&mVorbisDsp, &mVorbisInfo); michael@0: vorbis_block_init(&mVorbisDsp, &mVorbisBlock); michael@0: } michael@0: michael@0: mon.NotifyAll(); michael@0: michael@0: return ret == 0 ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: void VorbisTrackEncoder::WriteLacing(nsTArray *aOutput, int32_t aLacing) michael@0: { michael@0: while (aLacing >= 255) { michael@0: aLacing -= 255; michael@0: aOutput->AppendElement(255); michael@0: } michael@0: aOutput->AppendElement((uint8_t)aLacing); michael@0: } michael@0: michael@0: already_AddRefed michael@0: VorbisTrackEncoder::GetMetadata() michael@0: { michael@0: { michael@0: // Wait if encoder is not initialized. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: while (!mCanceled && !mInitialized) { michael@0: mon.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Vorbis codec specific data michael@0: // http://matroska.org/technical/specs/codecid/index.html michael@0: nsRefPtr meta = new VorbisMetadata(); michael@0: meta->mBitDepth = 32; // float for desktop michael@0: meta->mChannels = mChannels; michael@0: meta->mSamplingFrequency = mSamplingRate; michael@0: ogg_packet header; michael@0: ogg_packet header_comm; michael@0: ogg_packet header_code; michael@0: // Add comment michael@0: vorbis_comment vorbisComment; michael@0: vorbis_comment_init(&vorbisComment); michael@0: vorbis_comment_add_tag(&vorbisComment, "ENCODER", michael@0: NS_LITERAL_CSTRING("Mozilla VorbisTrackEncoder " MOZ_APP_UA_VERSION).get()); michael@0: vorbis_analysis_headerout(&mVorbisDsp, &vorbisComment, michael@0: &header,&header_comm, &header_code); michael@0: vorbis_comment_clear(&vorbisComment); michael@0: // number of distinct packets - 1 michael@0: meta->mData.AppendElement(2); michael@0: // Xiph-style lacing header.bytes, header_comm.bytes michael@0: WriteLacing(&(meta->mData), header.bytes); michael@0: WriteLacing(&(meta->mData), header_comm.bytes); michael@0: michael@0: // Append the three packets michael@0: meta->mData.AppendElements(header.packet, header.bytes); michael@0: meta->mData.AppendElements(header_comm.packet, header_comm.bytes); michael@0: meta->mData.AppendElements(header_code.packet, header_code.bytes); michael@0: michael@0: return meta.forget(); michael@0: } michael@0: michael@0: void michael@0: VorbisTrackEncoder::GetEncodedFrames(EncodedFrameContainer& aData) michael@0: { michael@0: // vorbis does some data preanalysis, then divvies up blocks for michael@0: // more involved (potentially parallel) processing. Get a single michael@0: // block for encoding now. michael@0: while (vorbis_analysis_blockout(&mVorbisDsp, &mVorbisBlock) == 1) { michael@0: ogg_packet oggPacket; michael@0: if (vorbis_analysis(&mVorbisBlock, &oggPacket) == 0) { michael@0: VORBISLOG("vorbis_analysis_blockout block size %d", oggPacket.bytes); michael@0: EncodedFrame* audiodata = new EncodedFrame(); michael@0: audiodata->SetFrameType(EncodedFrame::VORBIS_AUDIO_FRAME); michael@0: nsTArray frameData; michael@0: frameData.AppendElements(oggPacket.packet, oggPacket.bytes); michael@0: audiodata->SwapInFrameData(frameData); michael@0: aData.AppendEncodedFrame(audiodata); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: VorbisTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) michael@0: { michael@0: if (mEosSetInEncoder) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoPtr sourceSegment; michael@0: sourceSegment = new AudioSegment(); michael@0: { michael@0: // Move all the samples from mRawSegment to sourceSegment. We only hold michael@0: // the monitor in this block. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: // Wait if mEncoder is not initialized, or when not enough raw data, but is michael@0: // not the end of stream nor is being canceled. michael@0: while (!mCanceled && mRawSegment.GetDuration() < GetPacketDuration() && michael@0: !mEndOfStream) { michael@0: mon.Wait(); michael@0: } michael@0: VORBISLOG("GetEncodedTrack passes wait, duration is %lld\n", michael@0: mRawSegment.GetDuration()); michael@0: if (mCanceled || mEncodingComplete) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: sourceSegment->AppendFrom(&mRawSegment); michael@0: } michael@0: michael@0: if (mEndOfStream && (sourceSegment->GetDuration() == 0) michael@0: && !mEosSetInEncoder) { michael@0: mEncodingComplete = true; michael@0: mEosSetInEncoder = true; michael@0: VORBISLOG("[Vorbis] Done encoding."); michael@0: vorbis_analysis_wrote(&mVorbisDsp, 0); michael@0: GetEncodedFrames(aData); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Start encoding data. michael@0: AudioSegment::ChunkIterator iter(*sourceSegment); michael@0: michael@0: AudioDataValue **vorbisBuffer = michael@0: vorbis_analysis_buffer(&mVorbisDsp, (int)sourceSegment->GetDuration()); michael@0: michael@0: int framesCopied = 0; michael@0: nsAutoTArray interleavedPcm; michael@0: nsAutoTArray nonInterleavedPcm; michael@0: interleavedPcm.SetLength(sourceSegment->GetDuration() * mChannels); michael@0: nonInterleavedPcm.SetLength(sourceSegment->GetDuration() * mChannels); michael@0: while (!iter.IsEnded()) { michael@0: AudioChunk chunk = *iter; michael@0: int frameToCopy = chunk.GetDuration(); michael@0: if (!chunk.IsNull()) { michael@0: InterleaveTrackData(chunk, frameToCopy, mChannels, michael@0: interleavedPcm.Elements() + framesCopied * mChannels); michael@0: } else { // empty data michael@0: memset(interleavedPcm.Elements() + framesCopied * mChannels, 0, michael@0: frameToCopy * mChannels * sizeof(AudioDataValue)); michael@0: } michael@0: framesCopied += frameToCopy; michael@0: iter.Next(); michael@0: } michael@0: // De-interleave the interleavedPcm. michael@0: DeInterleaveTrackData(interleavedPcm.Elements(), framesCopied, mChannels, michael@0: nonInterleavedPcm.Elements()); michael@0: // Copy the nonInterleavedPcm to vorbis buffer. michael@0: for(uint8_t i = 0; i < mChannels; ++i) { michael@0: memcpy(vorbisBuffer[i], nonInterleavedPcm.Elements() + framesCopied * i, michael@0: framesCopied * sizeof(AudioDataValue)); michael@0: } michael@0: michael@0: // Now the vorbisBuffer contain the all data in non-interleaved. michael@0: // Tell the library how much we actually submitted. michael@0: vorbis_analysis_wrote(&mVorbisDsp, framesCopied); michael@0: VORBISLOG("vorbis_analysis_wrote framesCopied %d\n", framesCopied); michael@0: GetEncodedFrames(aData); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla