Bug 945863 - Handle CodecDelay and SeekPreroll for Opus in WebM. r=kinetik
authorJan Gerber <j@mailb.org>
Tue, 10 Dec 2013 10:36:05 +0100
changeset 159916 3b6dfc1090b0016696fd24a1c56de1487a6b4fed
parent 159915 59710abd0c169ee57252bff47aec74edb4a841ea
child 159917 ac002daf081b3cc23df10841806ca24ec55f1ce7
push idunknown
push userunknown
push dateunknown
reviewerskinetik
bugs945863
milestone29.0a1
Bug 945863 - Handle CodecDelay and SeekPreroll for Opus in WebM. r=kinetik
content/media/webm/WebMReader.cpp
content/media/webm/WebMReader.h
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -6,16 +6,17 @@
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #include "WebMReader.h"
 #include "WebMBufferedParser.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "VorbisUtils.h"
+#include <algorithm>
 
 #define VPX_DONT_DEFINE_STDINT_TYPES
 #include "vpx/vp8dx.h"
 #include "vpx/vpx_decoder.h"
 
 #include "OggReader.h"
 
 using mozilla::NesteggPacketHolder;
@@ -142,16 +143,17 @@ WebMReader::WebMReader(AbstractMediaDeco
   : MediaDecoderReader(aDecoder),
   mContext(nullptr),
   mPacketCount(0),
   mChannels(0),
 #ifdef MOZ_OPUS
   mOpusParser(nullptr),
   mOpusDecoder(nullptr),
   mSkip(0),
+  mSeekPreroll(0),
 #endif
   mVideoTrack(0),
   mAudioTrack(0),
   mAudioStartUsec(-1),
   mAudioFrames(0),
   mHasVideo(false),
   mHasAudio(false)
 {
@@ -213,20 +215,30 @@ nsresult WebMReader::ResetDecode()
 {
   mAudioFrames = 0;
   mAudioStartUsec = -1;
   nsresult res = NS_OK;
   if (NS_FAILED(MediaDecoderReader::ResetDecode())) {
     res = NS_ERROR_FAILURE;
   }
 
-  // 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);
+  if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
+    // 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);
+#ifdef MOZ_OPUS
+  } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
+    if (mOpusDecoder) {
+      // Reset the decoder.
+      opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE);
+      mSkip = mOpusParser->mPreSkip;
+    }
+#endif
+  }
 
   mVideoPackets.Reset();
   mAudioPackets.Reset();
 
   return res;
 }
 
 void WebMReader::Cleanup()
@@ -426,16 +438,18 @@ nsresult WebMReader::ReadMetadata(MediaI
           Cleanup();
           return NS_ERROR_FAILURE;
         }
 
         mInfo.mAudio.mRate = mOpusParser->mRate;
 
         mInfo.mAudio.mChannels = mOpusParser->mChannels;
         mInfo.mAudio.mChannels = mInfo.mAudio.mChannels > 2 ? 2 : mInfo.mAudio.mChannels;
+        mChannels = mInfo.mAudio.mChannels;
+        mSeekPreroll = params.seek_preroll;
 #endif
       } else {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
     }
   }
 
@@ -622,33 +636,34 @@ bool WebMReader::DecodeAudioPacket(neste
 #else
       int ret = opus_multistream_decode(mOpusDecoder,
                                         data, length,
                                         buffer, frames, false);
 #endif
       if (ret < 0)
         return false;
       NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
+      CheckedInt64 startTime = tstamp_usecs;
 
       // Trim the initial frames while the decoder is settling.
       if (mSkip > 0) {
         int32_t skipFrames = std::min(mSkip, frames);
         if (skipFrames == frames) {
           // discard the whole packet
           mSkip -= frames;
           LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames"
                              " (whole packet)", frames));
           return true;
         }
         int32_t keepFrames = frames - skipFrames;
         int samples = keepFrames * channels;
         nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
         for (int i = 0; i < samples; i++)
           trimBuffer[i] = buffer[skipFrames*channels + i];
-
+        startTime = startTime + FramesToUsecs(skipFrames, rate);
         frames = keepFrames;
         buffer = trimBuffer;
 
         mSkip -= skipFrames;
         LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames));
       }
 
       int64_t discardPadding = 0;
@@ -703,24 +718,22 @@ bool WebMReader::DecodeAudioPacket(neste
         OggReader::DownmixToStereo(buffer, channels, frames);
       }
 
       CheckedInt64 duration = FramesToUsecs(frames, rate);
       if (!duration.isValid()) {
         NS_WARNING("Int overflow converting WebM audio duration");
         return false;
       }
-
-      CheckedInt64 time = tstamp_usecs;
+      CheckedInt64 time = startTime - (mCodecDelay / NS_PER_USEC);
       if (!time.isValid()) {
-        NS_WARNING("Int overflow adding total_duration and tstamp_usecs");
+        NS_WARNING("Int overflow shifting tstamp by codec delay");
         nestegg_free_packet(aPacket);
         return false;
       };
-
       AudioQueue().Push(new AudioData(mDecoder->GetResource()->Tell(),
                                      time.value(),
                                      duration.value(),
                                      frames,
                                      buffer.forget(),
                                      mChannels));
 
       mAudioFrames += frames;
@@ -977,17 +990,21 @@ nsresult WebMReader::Seek(int64_t aTarge
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
                      this, mDecoder, aTarget/1000000.0));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
   uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
-  int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_USEC);
+  uint64_t target = aTarget * NS_PER_USEC;
+  if (mSeekPreroll) {
+    target = std::max(static_cast<uint64_t>(aStartTime * NS_PER_USEC), target - mSeekPreroll);
+  }
+  int r = nestegg_track_seek(mContext, trackToSeek, target);
   if (r != 0) {
     return NS_ERROR_FAILURE;
   }
   return DecodeToTarget(aTarget);
 }
 
 nsresult WebMReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
 {
--- a/content/media/webm/WebMReader.h
+++ b/content/media/webm/WebMReader.h
@@ -192,16 +192,17 @@ private:
   uint32_t mChannels;
 
 
 #ifdef MOZ_OPUS
   // Opus decoder state
   nsAutoPtr<OpusParser> mOpusParser;
   OpusMSDecoder *mOpusDecoder;
   int mSkip;        // Number of samples left to trim before playback.
+  uint64_t mSeekPreroll; // Number of nanoseconds that must be discarded after seeking.
 #endif
 
   // Queue of video and audio packets that have been read but not decoded. These
   // must only be accessed from the state machine thread.
   WebMPacketQueue mVideoPackets;
   WebMPacketQueue mAudioPackets;
 
   // Index of video and audio track to play