nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) { 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); if (r != 0) { return NS_ERROR_FAILURE; } #ifdef MOZ_DASH // Find next cluster index; MediaResource* resource = mDecoder->GetResource(); int64_t newOffset = resource->Tell(); for (uint32_t i = 1; i < mClusterByteRanges.Length(); i++) { if (newOffset < mClusterByteRanges[i].mStart) { mNextCluster = i; LOG(PR_LOG_DEBUG, ("WebMReader [%p] for decoder [%p] updating mNextCluster to [%d] " "after seek to offset [%lld]", this, mDecoder, mNextCluster, resource->Tell())); break; } } #endif return DecodeToTarget(aTarget); }
/* * If we're not at end of stream, read |aNumBytes| from the media resource, * put it in |aData|, and return true. * Otherwise, put as much data as is left into |aData|, set |aNumBytes| to the * amount of data we have left, and return false. */ nsresult AppleMP3Reader::Read(uint32_t *aNumBytes, char *aData) { MediaResource *resource = mDecoder->GetResource(); // Loop until we have all the data asked for, or we've reached EOS uint32_t totalBytes = 0; uint32_t numBytes; do { uint32_t bytesWanted = *aNumBytes - totalBytes; nsresult rv = resource->Read(aData + totalBytes, bytesWanted, &numBytes); totalBytes += numBytes; if (NS_FAILED(rv)) { *aNumBytes = 0; return NS_ERROR_FAILURE; } } while(totalBytes < *aNumBytes && numBytes); *aNumBytes = totalBytes; // We will have read some data in the last iteration iff we filled the buffer. // XXX Maybe return a better value than NS_ERROR_FAILURE? return numBytes ? NS_OK : NS_ERROR_FAILURE; }
bool WebMReader::IsDataCachedAtEndOfSubsegments() { MediaResource* resource = mDecoder->GetResource(); NS_ENSURE_TRUE(resource, false); if (resource->IsDataCachedToEndOfResource(0)) { return true; } if (mClusterByteRanges.IsEmpty()) { return false; } nsTArray<MediaByteRange> ranges; nsresult rv = resource->GetCachedRanges(ranges); NS_ENSURE_SUCCESS(rv, false); if (ranges.IsEmpty()) { return false; } // Return true if data at the end of the final subsegment is cached. uint32_t finalSubsegmentIndex = mClusterByteRanges.Length()-1; uint64_t finalSubEndOffset = mClusterByteRanges[finalSubsegmentIndex].mEnd; uint32_t finalRangeIndex = ranges.Length()-1; uint64_t finalRangeStartOffset = ranges[finalRangeIndex].mStart; uint64_t finalRangeEndOffset = ranges[finalRangeIndex].mEnd; return (finalRangeStartOffset < finalSubEndOffset && finalSubEndOffset <= finalRangeEndOffset); }
void GStreamerReader::PlayBinSourceSetup(GstAppSrc* aSource) { mSource = GST_APP_SRC(aSource); gst_app_src_set_callbacks(mSource, &mSrcCallbacks, (gpointer) this, nullptr); MediaResource* resource = mDecoder->GetResource(); /* do a short read to trigger a network request so that GetLength() below * returns something meaningful and not -1 */ char buf[512]; unsigned int size = 0; resource->Read(buf, sizeof(buf), &size); resource->Seek(SEEK_SET, 0); /* now we should have a length */ int64_t resourceLength = resource->GetLength(); gst_app_src_set_size(mSource, resourceLength); if (resource->IsDataCachedToEndOfResource(0) || (resourceLength != -1 && resourceLength <= SHORT_FILE_SIZE)) { /* let the demuxer work in pull mode for local files (or very short files) * so that we get optimal seeking accuracy/performance */ LOG(PR_LOG_DEBUG, ("configuring random access, len %lld", resourceLength)); gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_RANDOM_ACCESS); } else { /* make the demuxer work in push mode so that seeking is kept to a minimum */ LOG(PR_LOG_DEBUG, ("configuring push mode, len %lld", resourceLength)); gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_SEEKABLE); } }
nsresult GStreamerReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) { if (!mInfo.HasValidMedia()) { return NS_OK; } #if GST_VERSION_MAJOR == 0 GstFormat format = GST_FORMAT_TIME; #endif MediaResource* resource = mDecoder->GetResource(); nsTArray<MediaByteRange> ranges; resource->GetCachedRanges(ranges); if (resource->IsDataCachedToEndOfResource(0)) { /* fast path for local or completely cached files */ gint64 duration = 0; { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); duration = mDecoder->GetMediaDuration(); } double end = (double) duration / GST_MSECOND; LOG(PR_LOG_DEBUG, "complete range [0, %f] for [0, %li]", end, resource->GetLength()); aBuffered->Add(0, end); return NS_OK; } for(uint32_t index = 0; index < ranges.Length(); index++) { int64_t startOffset = ranges[index].mStart; int64_t endOffset = ranges[index].mEnd; gint64 startTime, endTime; #if GST_VERSION_MAJOR >= 1 if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, startOffset, GST_FORMAT_TIME, &startTime)) continue; if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, endOffset, GST_FORMAT_TIME, &endTime)) continue; #else if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, startOffset, &format, &startTime) || format != GST_FORMAT_TIME) continue; if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, endOffset, &format, &endTime) || format != GST_FORMAT_TIME) continue; #endif double start = (double) GST_TIME_AS_USECONDS (startTime) / GST_MSECOND; double end = (double) GST_TIME_AS_USECONDS (endTime) / GST_MSECOND; LOG(PR_LOG_DEBUG, "adding range [%f, %f] for [%li %li] size %li", start, end, startOffset, endOffset, resource->GetLength()); aBuffered->Add(start, end); } return NS_OK; }
/** * If this stream is an MP3, we want to parse the headers to estimate the * stream duration. */ nsresult GStreamerReader::ParseMP3Headers() { MediaResource *resource = mDecoder->GetResource(); const uint32_t MAX_READ_BYTES = 4096; uint64_t offset = 0; char bytes[MAX_READ_BYTES]; uint32_t bytesRead; do { nsresult rv = resource->ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE); mMP3FrameParser.Parse(bytes, bytesRead, offset); offset += bytesRead; } while (!mMP3FrameParser.ParsedHeaders()); if (mMP3FrameParser.IsMP3()) { mLastParserDuration = mMP3FrameParser.GetDuration(); mDataOffset = mMP3FrameParser.GetMP3Offset(); // Update GStreamer's stream length in case we found any ID3 headers to // ignore. gst_app_src_set_size(mSource, GetDataLength()); } return NS_OK; }
void nsGStreamerReader::ReadAndPushData(guint aLength) { MediaResource* resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); nsresult rv = NS_OK; GstBuffer *buffer = gst_buffer_new_and_alloc(aLength); guint8 *data = GST_BUFFER_DATA(buffer); uint32_t size = 0, bytesRead = 0; while(bytesRead < aLength) { rv = resource->Read(reinterpret_cast<char*>(data + bytesRead), aLength - bytesRead, &size); if (NS_FAILED(rv) || size == 0) break; bytesRead += size; } GST_BUFFER_SIZE(buffer) = bytesRead; mByteOffset += bytesRead; GstFlowReturn ret = gst_app_src_push_buffer(mSource, gst_buffer_ref(buffer)); if (ret != GST_FLOW_OK) LOG(PR_LOG_ERROR, ("ReadAndPushData push ret %s", gst_flow_get_name(ret))); if (GST_BUFFER_SIZE (buffer) < aLength) /* If we read less than what we wanted, we reached the end */ gst_app_src_end_of_stream(mSource); gst_buffer_unref(buffer); }
// Functions for reading and seeking using MediaResource required for // nestegg_io. The 'user data' passed to these functions is the // decoder from which the media resource is obtained. static int webm_read(void *aBuffer, size_t aLength, void *aUserData) { NS_ASSERTION(aUserData, "aUserData must point to a valid nsBuiltinDecoder"); nsBuiltinDecoder* decoder = reinterpret_cast<nsBuiltinDecoder*>(aUserData); MediaResource* resource = decoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); nsresult rv = NS_OK; bool eof = false; char *p = static_cast<char *>(aBuffer); while (NS_SUCCEEDED(rv) && aLength > 0) { PRUint32 bytes = 0; rv = resource->Read(p, aLength, &bytes); if (bytes == 0) { eof = true; break; } decoder->NotifyBytesConsumed(bytes); aLength -= bytes; p += bytes; } return NS_FAILED(rv) ? -1 : eof ? 0 : 1; }
nsresult nsWebMReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime) { MediaResource* resource = mDecoder->GetResource(); uint64_t timecodeScale; if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) { return NS_OK; } // Special case completely cached files. This also handles local files. if (resource->IsDataCachedToEndOfResource(0)) { uint64_t duration = 0; if (nestegg_duration(mContext, &duration) == 0) { aBuffered->Add(0, duration / NS_PER_S); } } else { MediaResource* resource = mDecoder->GetResource(); nsTArray<MediaByteRange> ranges; nsresult res = resource->GetCachedRanges(ranges); NS_ENSURE_SUCCESS(res, res); PRInt64 startTimeOffsetNS = aStartTime * NS_PER_USEC; for (PRUint32 index = 0; index < ranges.Length(); index++) { mBufferedState->CalculateBufferedForRange(aBuffered, ranges[index].mStart, ranges[index].mEnd, timecodeScale, startTimeOffsetNS); } } return NS_OK; }
gboolean GStreamerReader::SeekData(GstAppSrc* aSrc, guint64 aOffset) { aOffset += mDataOffset; ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); MediaResource* resource = mDecoder->GetResource(); int64_t resourceLength = resource->GetLength(); if (gst_app_src_get_size(mSource) == -1) { /* It's possible that we didn't know the length when we initialized mSource * but maybe we do now */ gst_app_src_set_size(mSource, GetDataLength()); } nsresult rv = NS_ERROR_FAILURE; if (aOffset < static_cast<guint64>(resourceLength)) { rv = resource->Seek(SEEK_SET, aOffset); } if (NS_FAILED(rv)) { LOG(PR_LOG_ERROR, "seek at %lu failed", aOffset); } else { MOZ_ASSERT(aOffset == static_cast<guint64>(resource->Tell())); } return NS_SUCCEEDED(rv); }
static int64_t webm_tell(void *aUserData) { NS_ASSERTION(aUserData, "aUserData must point to a valid AbstractMediaDecoder"); AbstractMediaDecoder* decoder = reinterpret_cast<AbstractMediaDecoder*>(aUserData); MediaResource* resource = decoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); return resource->Tell(); }
static int webm_seek(int64_t aOffset, int aWhence, void *aUserData) { NS_ASSERTION(aUserData, "aUserData must point to a valid AbstractMediaDecoder"); AbstractMediaDecoder* decoder = reinterpret_cast<AbstractMediaDecoder*>(aUserData); MediaResource* resource = decoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); nsresult rv = resource->Seek(aWhence, aOffset); return NS_SUCCEEDED(rv) ? 0 : -1; }
void MediaDecoder::PinForSeek() { MediaResource* resource = GetResource(); if (!resource || mPinnedForSeek) { return; } mPinnedForSeek = true; resource->Pin(); }
void MediaDecoder::UnpinForSeek() { MediaResource* resource = GetResource(); if (!resource || !mPinnedForSeek) { return; } mPinnedForSeek = false; resource->Unpin(); }
void MediaDecoder::UnpinForSeek() { MOZ_ASSERT(NS_IsMainThread()); MediaResource* resource = GetResource(); if (!resource || !mPinnedForSeek) { return; } mPinnedForSeek = false; resource->Unpin(); }
nsresult WebMReader::GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime) { MediaResource* resource = mDecoder->GetResource(); uint64_t timecodeScale; if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) { return NS_OK; } // Special case completely cached files. This also handles local files. bool isFullyCached = resource->IsDataCachedToEndOfResource(0); if (isFullyCached) { uint64_t duration = 0; if (nestegg_duration(mContext, &duration) == 0) { aBuffered->Add(0, duration / NS_PER_S); } } uint32_t bufferedLength = 0; aBuffered->GetLength(&bufferedLength); // Either we the file is not fully cached, or we couldn't find a duration in // the WebM bitstream. if (!isFullyCached || !bufferedLength) { MediaResource* resource = mDecoder->GetResource(); nsTArray<MediaByteRange> ranges; nsresult res = resource->GetCachedRanges(ranges); NS_ENSURE_SUCCESS(res, res); for (uint32_t index = 0; index < ranges.Length(); index++) { uint64_t start, end; bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart, ranges[index].mEnd, &start, &end); if (rv) { double startTime = start * timecodeScale / NS_PER_S - aStartTime; double endTime = end * timecodeScale / NS_PER_S - aStartTime; // If this range extends to the end of the file, the true end time // is the file's duration. if (resource->IsDataCachedToEndOfResource(ranges[index].mStart)) { uint64_t duration = 0; if (nestegg_duration(mContext, &duration) == 0) { endTime = duration / NS_PER_S; } } aBuffered->Add(startTime, endTime); } } } return NS_OK; }
nsresult nsGStreamerReader::GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime) { if (!mInfo.mHasVideo && !mInfo.mHasAudio) { return NS_OK; } GstFormat format = GST_FORMAT_TIME; MediaResource* resource = mDecoder->GetResource(); gint64 resourceLength = resource->GetLength(); nsTArray<MediaByteRange> ranges; resource->GetCachedRanges(ranges); if (mDecoder->OnStateMachineThread()) /* Report the position from here while buffering as we can't report it from * the gstreamer threads that are actually reading from the resource */ NotifyBytesConsumed(); if (resource->IsDataCachedToEndOfResource(0)) { /* fast path for local or completely cached files */ gint64 duration = 0; GstFormat format = GST_FORMAT_TIME; duration = QueryDuration(); double end = (double) duration / GST_MSECOND; LOG(PR_LOG_DEBUG, ("complete range [0, %f] for [0, %li]", end, resourceLength)); aBuffered->Add(0, end); return NS_OK; } for(uint32_t index = 0; index < ranges.Length(); index++) { int64_t startOffset = ranges[index].mStart; int64_t endOffset = ranges[index].mEnd; gint64 startTime, endTime; if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, startOffset, &format, &startTime) || format != GST_FORMAT_TIME) continue; if (!gst_element_query_convert(GST_ELEMENT(mPlayBin), GST_FORMAT_BYTES, endOffset, &format, &endTime) || format != GST_FORMAT_TIME) continue; double start = start = (double) GST_TIME_AS_USECONDS (startTime) / GST_MSECOND; double end = (double) GST_TIME_AS_USECONDS (endTime) / GST_MSECOND; LOG(PR_LOG_DEBUG, ("adding range [%f, %f] for [%li %li] size %li", start, end, startOffset, endOffset, resourceLength)); aBuffered->Add(start, end); } return NS_OK; }
nsresult nsRawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); MediaResource *resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); uint32_t frame = mCurrentFrame; if (aTime >= UINT_MAX) return NS_ERROR_FAILURE; mCurrentFrame = aTime * mFrameRate / USECS_PER_S; CheckedUint32 offset = CheckedUint32(mCurrentFrame) * mFrameSize; offset += sizeof(nsRawVideoHeader); NS_ENSURE_TRUE(offset.isValid(), NS_ERROR_FAILURE); nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value()); NS_ENSURE_SUCCESS(rv, rv); mVideoQueue.Erase(); while(mVideoQueue.GetSize() == 0) { bool keyframeSkip = false; if (!DecodeVideoFrame(keyframeSkip, 0)) { mCurrentFrame = frame; return NS_ERROR_FAILURE; } { mozilla::ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor()); if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) { mCurrentFrame = frame; return NS_ERROR_FAILURE; } } nsAutoPtr<VideoData> video(mVideoQueue.PeekFront()); if (video && video->mEndTime < aTime) { mVideoQueue.PopFront(); video = nullptr; } else { video.forget(); } } return NS_OK; }
static bool Read(Decoder *aDecoder, char *aBuffer, int64_t aOffset, uint32_t aCount, uint32_t* aBytes) { MediaResource *resource = GetResource(aDecoder); if (aOffset != resource->Tell()) { nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); if (NS_FAILED(rv)) { return false; } } nsresult rv = resource->Read(aBuffer, aCount, aBytes); if (NS_FAILED(rv)) { return false; } return true; }
nsresult RawReader::SeekInternal(int64_t aTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); MediaResource *resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); uint32_t frame = mCurrentFrame; if (aTime >= UINT_MAX) return NS_ERROR_FAILURE; mCurrentFrame = aTime * mFrameRate / USECS_PER_S; CheckedUint32 offset = CheckedUint32(mCurrentFrame) * mFrameSize; offset += sizeof(RawVideoHeader); NS_ENSURE_TRUE(offset.isValid(), NS_ERROR_FAILURE); nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value()); NS_ENSURE_SUCCESS(rv, rv); mVideoQueue.Reset(); while(mVideoQueue.GetSize() == 0) { bool keyframeSkip = false; if (!DecodeVideoFrame(keyframeSkip, 0)) { mCurrentFrame = frame; return NS_ERROR_FAILURE; } { ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor()); if (mDecoder->IsShutdown()) { mCurrentFrame = frame; return NS_ERROR_FAILURE; } } if (mVideoQueue.PeekFront() && mVideoQueue.PeekFront()->GetEndTime() < aTime) { nsRefPtr<VideoData> releaseMe = mVideoQueue.PopFront(); } } return NS_OK; }
void GStreamerReader::ReadAndPushData(guint aLength) { MediaResource* resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); nsresult rv = NS_OK; GstBuffer* buffer = gst_buffer_new_and_alloc(aLength); #if GST_VERSION_MAJOR >= 1 GstMapInfo info; gst_buffer_map(buffer, &info, GST_MAP_WRITE); guint8 *data = info.data; #else guint8* data = GST_BUFFER_DATA(buffer); #endif uint32_t size = 0, bytesRead = 0; while(bytesRead < aLength) { rv = resource->Read(reinterpret_cast<char*>(data + bytesRead), aLength - bytesRead, &size); if (NS_FAILED(rv) || size == 0) break; bytesRead += size; } #if GST_VERSION_MAJOR >= 1 gst_buffer_unmap(buffer, &info); gst_buffer_set_size(buffer, bytesRead); #else GST_BUFFER_SIZE(buffer) = bytesRead; #endif GstFlowReturn ret = gst_app_src_push_buffer(mSource, gst_buffer_ref(buffer)); if (ret != GST_FLOW_OK) { LOG(PR_LOG_ERROR, ("ReadAndPushData push ret %s", gst_flow_get_name(ret))); } if (bytesRead < aLength) { /* If we read less than what we wanted, we reached the end */ gst_app_src_end_of_stream(mSource); } gst_buffer_unref(buffer); }
void nsGStreamerReader::PlayBinSourceSetup(GstAppSrc *aSource) { mSource = GST_APP_SRC(aSource); gst_app_src_set_callbacks(mSource, &mSrcCallbacks, (gpointer) this, NULL); MediaResource* resource = mDecoder->GetResource(); int64_t len = resource->GetLength(); gst_app_src_set_size(mSource, len); if (resource->IsDataCachedToEndOfResource(0) || (len != -1 && len <= SHORT_FILE_SIZE)) { /* let the demuxer work in pull mode for local files (or very short files) * so that we get optimal seeking accuracy/performance */ LOG(PR_LOG_ERROR, ("configuring random access")); gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_RANDOM_ACCESS); } else { /* make the demuxer work in push mode so that seeking is kept to a minimum */ gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_SEEKABLE); } }
/* * If we're not at end of stream, read |aNumBytes| from the media resource, * put it in |aData|, and return true. * Otherwise, put as much data as is left into |aData|, set |aNumBytes| to the * amount of data we have left, and return false. * * This function also passes the read data on to the MP3 frame parser for * stream duration estimation. */ nsresult AppleMP3Reader::ReadAndNotify(uint32_t *aNumBytes, char *aData) { MediaResource *resource = mDecoder->GetResource(); uint64_t offset = resource->Tell(); // Loop until we have all the data asked for, or we've reached EOS uint32_t totalBytes = 0; uint32_t numBytes; do { uint32_t bytesWanted = *aNumBytes - totalBytes; nsresult rv = resource->Read(aData + totalBytes, bytesWanted, &numBytes); totalBytes += numBytes; if (NS_FAILED(rv)) { *aNumBytes = 0; return NS_ERROR_FAILURE; } } while(totalBytes < *aNumBytes && numBytes); // Pass the buffer to the MP3 frame parser to improve our duration estimate. if (mMP3FrameParser.IsMP3()) { mMP3FrameParser.Parse(aData, totalBytes, offset); uint64_t duration = mMP3FrameParser.GetDuration(); if (duration != mDuration) { LOGD("Updating media duration to %lluus\n", duration); mDuration = duration; ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mDecoder->UpdateEstimatedMediaDuration(duration); } } *aNumBytes = totalBytes; // We will have read some data in the last iteration iff we filled the buffer. // XXX Maybe return a better value than NS_ERROR_FAILURE? return numBytes ? NS_OK : NS_ERROR_FAILURE; }
gboolean nsGStreamerReader::SeekData(GstAppSrc *aSrc, guint64 aOffset) { ReentrantMonitorAutoEnter mon(mGstThreadsMonitor); MediaResource* resource = mDecoder->GetResource(); if (gst_app_src_get_size(mSource) == -1) /* It's possible that we didn't know the length when we initialized mSource * but maybe we do now */ gst_app_src_set_size(mSource, resource->GetLength()); nsresult rv = NS_ERROR_FAILURE; if (aOffset < resource->GetLength()) rv = resource->Seek(SEEK_SET, aOffset); if (NS_SUCCEEDED(rv)) mByteOffset = mLastReportedByteOffset = aOffset; else LOG(PR_LOG_ERROR, ("seek at %lu failed", aOffset)); return NS_SUCCEEDED(rv); }
/** * If this stream is an MP3, we want to parse the headers to estimate the * stream duration. */ nsresult GStreamerReader::ParseMP3Headers() { MediaResource *resource = mDecoder->GetResource(); const uint32_t MAX_READ_BYTES = 4096; uint64_t offset = 0; char bytes[MAX_READ_BYTES]; uint32_t bytesRead; do { nsresult rv = resource->ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE); mMP3FrameParser.Parse(bytes, bytesRead, offset); offset += bytesRead; } while (!mMP3FrameParser.ParsedHeaders()); if (mMP3FrameParser.IsMP3()) { mLastParserDuration = mMP3FrameParser.GetDuration(); } return NS_OK; }
nsresult MediaOmxReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered, int64_t aStartTime) { if (!mOmxDecoder.get()) return NS_OK; MediaResource* stream = mOmxDecoder->GetResource(); int64_t durationUs = 0; mOmxDecoder->GetDuration(&durationUs); // Nothing to cache if the media takes 0us to play. if (!durationUs) return NS_OK; // Special case completely cached files. This also handles local files. if (stream->IsDataCachedToEndOfResource(0)) { aBuffered->Add(0, durationUs); return NS_OK; } int64_t totalBytes = stream->GetLength(); // If we can't determine the total size, pretend that we have nothing // buffered. This will put us in a state of eternally-low-on-undecoded-data // which is not get, but about the best we can do. if (totalBytes == -1) return NS_OK; int64_t startOffset = stream->GetNextCachedData(0); while (startOffset >= 0) { int64_t endOffset = stream->GetCachedDataEnd(startOffset); // Bytes [startOffset..endOffset] are cached. NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered"); NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered"); uint64_t startUs = BytesToTime(startOffset, totalBytes, durationUs); uint64_t endUs = BytesToTime(endOffset, totalBytes, durationUs); if (startUs != endUs) { aBuffered->Add((double)startUs / USECS_PER_S, (double)endUs / USECS_PER_S); } startOffset = stream->GetNextCachedData(endOffset); } return NS_OK; }
nsresult nsDASHReader::GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime) { NS_ENSURE_ARG(aBuffered); MediaResource* resource = nullptr; nsBuiltinDecoder* decoder = nullptr; // Need to find intersect of |nsTimeRanges| for audio and video. nsTimeRanges audioBuffered, videoBuffered; uint32_t audioRangeCount, videoRangeCount; nsresult rv = NS_OK; // First, get buffered ranges for sub-readers. ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(), mDecoder->GetReentrantMonitor()); if (mAudioReader) { decoder = mAudioReader->GetDecoder(); NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER); resource = decoder->GetResource(); NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER); resource->Pin(); rv = mAudioReader->GetBuffered(&audioBuffered, aStartTime); NS_ENSURE_SUCCESS(rv, rv); resource->Unpin(); rv = audioBuffered.GetLength(&audioRangeCount); NS_ENSURE_SUCCESS(rv, rv); } if (mVideoReader) { decoder = mVideoReader->GetDecoder(); NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER); resource = decoder->GetResource(); NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER); resource->Pin(); rv = mVideoReader->GetBuffered(&videoBuffered, aStartTime); NS_ENSURE_SUCCESS(rv, rv); resource->Unpin(); rv = videoBuffered.GetLength(&videoRangeCount); NS_ENSURE_SUCCESS(rv, rv); } // Now determine buffered data for available sub-readers. if (mAudioReader && mVideoReader) { // Calculate intersecting ranges. for (uint32_t i = 0; i < audioRangeCount; i++) { // |A|udio, |V|ideo, |I|ntersect. double startA, startV, startI; double endA, endV, endI; rv = audioBuffered.Start(i, &startA); NS_ENSURE_SUCCESS(rv, rv); rv = audioBuffered.End(i, &endA); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t j = 0; j < videoRangeCount; j++) { rv = videoBuffered.Start(i, &startV); NS_ENSURE_SUCCESS(rv, rv); rv = videoBuffered.End(i, &endV); NS_ENSURE_SUCCESS(rv, rv); // If video block is before audio block, compare next video block. if (startA > endV) { continue; // If video block is after audio block, all of them are; compare next // audio block. } else if (endA < startV) { break; } // Calculate intersections of current audio and video blocks. startI = (startA > startV) ? startA : startV; endI = (endA > endV) ? endV : endA; aBuffered->Add(startI, endI); } } } else if (mAudioReader) { *aBuffered = audioBuffered; } else if (mVideoReader) { *aBuffered = videoBuffered; } else { return NS_ERROR_NOT_INITIALIZED; } return NS_OK; }
nsresult RawReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); MediaResource* resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); if (!ReadFromResource(resource, reinterpret_cast<uint8_t*>(&mMetadata), sizeof(mMetadata))) return NS_ERROR_FAILURE; // Validate the header if (!(mMetadata.headerPacketID == 0 /* Packet ID of 0 for the header*/ && mMetadata.codecID == RAW_ID /* "YUV" */ && mMetadata.majorVersion == 0 && mMetadata.minorVersion == 1)) return NS_ERROR_FAILURE; CheckedUint32 dummy = CheckedUint32(static_cast<uint32_t>(mMetadata.frameWidth)) * static_cast<uint32_t>(mMetadata.frameHeight); NS_ENSURE_TRUE(dummy.isValid(), NS_ERROR_FAILURE); if (mMetadata.aspectDenominator == 0 || mMetadata.framerateDenominator == 0) return NS_ERROR_FAILURE; // Invalid data // Determine and verify frame display size. float pixelAspectRatio = static_cast<float>(mMetadata.aspectNumerator) / mMetadata.aspectDenominator; nsIntSize display(mMetadata.frameWidth, mMetadata.frameHeight); ScaleDisplayByAspectRatio(display, pixelAspectRatio); mPicture = nsIntRect(0, 0, mMetadata.frameWidth, mMetadata.frameHeight); nsIntSize frameSize(mMetadata.frameWidth, mMetadata.frameHeight); if (!IsValidVideoRegion(frameSize, mPicture, display)) { // Video track's frame sizes will overflow. Fail. return NS_ERROR_FAILURE; } mInfo.mVideo.mDisplay = display; mFrameRate = static_cast<float>(mMetadata.framerateNumerator) / mMetadata.framerateDenominator; // Make some sanity checks if (mFrameRate > 45 || mFrameRate == 0 || pixelAspectRatio == 0 || mMetadata.frameWidth > 2000 || mMetadata.frameHeight > 2000 || mMetadata.chromaChannelBpp != 4 || mMetadata.lumaChannelBpp != 8 || mMetadata.colorspace != 1 /* 4:2:0 */) return NS_ERROR_FAILURE; mFrameSize = mMetadata.frameWidth * mMetadata.frameHeight * (mMetadata.lumaChannelBpp + mMetadata.chromaChannelBpp) / 8.0 + sizeof(RawPacketHeader); int64_t length = resource->GetLength(); if (length != -1) { ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor()); mDecoder->SetMediaDuration(USECS_PER_S * (length - sizeof(RawVideoHeader)) / (mFrameSize * mFrameRate)); } *aInfo = mInfo; *aTags = nullptr; return NS_OK; }
nsresult WebMReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) { MediaResource* resource = mDecoder->GetResource(); uint64_t timecodeScale; if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) { return NS_OK; } // Special case completely cached files. This also handles local files. bool isFullyCached = resource->IsDataCachedToEndOfResource(0); if (isFullyCached) { uint64_t duration = 0; if (nestegg_duration(mContext, &duration) == 0) { aBuffered->Add(0, duration / NS_PER_S); } } uint32_t bufferedLength = 0; aBuffered->GetLength(&bufferedLength); // Either we the file is not fully cached, or we couldn't find a duration in // the WebM bitstream. if (!isFullyCached || !bufferedLength) { MediaResource* resource = mDecoder->GetResource(); nsTArray<MediaByteRange> ranges; nsresult res = resource->GetCachedRanges(ranges); NS_ENSURE_SUCCESS(res, res); for (uint32_t index = 0; index < ranges.Length(); index++) { uint64_t start, end; bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart, ranges[index].mEnd, &start, &end); if (rv) { double startTime = start * timecodeScale / NS_PER_S - aStartTime; double endTime = end * timecodeScale / NS_PER_S - aStartTime; #ifdef MOZ_DASH // If this range extends to the end of a cluster, the true end time is // the cluster's end timestamp. Since WebM frames do not have an end // timestamp, a fully cached cluster must be reported with the correct // end time of its final frame. Otherwise, buffered ranges could be // reported with missing frames at cluster boundaries, specifically // boundaries where stream switching has occurred. if (!mClusterByteRanges.IsEmpty()) { for (uint32_t clusterIndex = 0; clusterIndex < (mClusterByteRanges.Length()-1); clusterIndex++) { if (ranges[index].mEnd >= mClusterByteRanges[clusterIndex].mEnd) { double clusterEndTime = mClusterByteRanges[clusterIndex+1].mStartTime / USEC_PER_S; if (endTime < clusterEndTime) { LOG(PR_LOG_DEBUG, ("End of cluster: endTime becoming %0.3fs", clusterEndTime)); endTime = clusterEndTime; } } } } #endif // If this range extends to the end of the file, the true end time // is the file's duration. if (resource->IsDataCachedToEndOfResource(ranges[index].mStart)) { uint64_t duration = 0; if (nestegg_duration(mContext, &duration) == 0) { endTime = duration / NS_PER_S; } } aBuffered->Add(startTime, endTime); } } } return NS_OK; }
void GStreamerReader::ReadAndPushData(guint aLength) { MediaResource* resource = mDecoder->GetResource(); NS_ASSERTION(resource, "Decoder has no media resource"); int64_t offset1 = resource->Tell(); unused << offset1; nsresult rv = NS_OK; GstBuffer* buffer = gst_buffer_new_and_alloc(aLength); #if GST_VERSION_MAJOR >= 1 GstMapInfo info; gst_buffer_map(buffer, &info, GST_MAP_WRITE); guint8 *data = info.data; #else guint8* data = GST_BUFFER_DATA(buffer); #endif uint32_t size = 0, bytesRead = 0; while(bytesRead < aLength) { rv = resource->Read(reinterpret_cast<char*>(data + bytesRead), aLength - bytesRead, &size); if (NS_FAILED(rv) || size == 0) break; bytesRead += size; } int64_t offset2 = resource->Tell(); unused << offset2; #if GST_VERSION_MAJOR >= 1 gst_buffer_unmap(buffer, &info); gst_buffer_set_size(buffer, bytesRead); #else GST_BUFFER_SIZE(buffer) = bytesRead; #endif GstFlowReturn ret = gst_app_src_push_buffer(mSource, gst_buffer_ref(buffer)); if (ret != GST_FLOW_OK) { LOG(PR_LOG_ERROR, "ReadAndPushData push ret %s(%d)", gst_flow_get_name(ret), ret); } if (NS_FAILED(rv)) { /* Terminate the stream if there is an error in reading */ LOG(PR_LOG_ERROR, "ReadAndPushData read error, rv=%x", rv); gst_app_src_end_of_stream(mSource); } else if (bytesRead < aLength) { /* If we read less than what we wanted, we reached the end */ LOG(PR_LOG_WARNING, "ReadAndPushData read underflow, " "bytesRead=%u, aLength=%u, offset(%lld,%lld)", bytesRead, aLength, offset1, offset2); gst_app_src_end_of_stream(mSource); } gst_buffer_unref(buffer); /* Ensure offset change is consistent in this function. * If there are other stream operations on another thread at the same time, * it will disturb the GStreamer state machine. */ MOZ_ASSERT(offset1 + bytesRead == offset2); }