diff -r 000000000000 -r 6474c204b198 content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,271 @@ +/* -*- 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 "MediaTaskQueue.h" +#include "nsThreadUtils.h" +#include "nsAutoPtr.h" +#include "ImageContainer.h" + +#include "mp4_demuxer/mp4_demuxer.h" +#include "FFmpegRuntimeLinker.h" + +#include "FFmpegH264Decoder.h" + +#define GECKO_FRAME_TYPE 0x00093CC0 + +typedef mozilla::layers::Image Image; +typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage; + +typedef mp4_demuxer::MP4Sample MP4Sample; + +namespace mozilla +{ + +FFmpegH264Decoder::FFmpegH264Decoder( + MediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback, + const mp4_demuxer::VideoDecoderConfig &aConfig, + ImageContainer* aImageContainer) + : FFmpegDataDecoder(aTaskQueue, AV_CODEC_ID_H264) + , mConfig(aConfig) + , mCallback(aCallback) + , mImageContainer(aImageContainer) +{ + MOZ_COUNT_CTOR(FFmpegH264Decoder); +} + +nsresult +FFmpegH264Decoder::Init() +{ + nsresult rv = FFmpegDataDecoder::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mCodecContext.get_buffer = AllocateBufferCb; + + return NS_OK; +} + +void +FFmpegH264Decoder::DecodeFrame(mp4_demuxer::MP4Sample* aSample) +{ + AVPacket packet; + av_init_packet(&packet); + + packet.data = &(*aSample->data)[0]; + packet.size = aSample->data->size(); + packet.dts = aSample->decode_timestamp; + packet.pts = aSample->composition_timestamp; + packet.flags = aSample->is_sync_point ? AV_PKT_FLAG_KEY : 0; + packet.pos = aSample->byte_offset; + + nsAutoPtr frame(avcodec_alloc_frame()); + avcodec_get_frame_defaults(frame); + + int decoded; + int bytesConsumed = + avcodec_decode_video2(&mCodecContext, frame, &decoded, &packet); + + if (bytesConsumed < 0) { + NS_WARNING("FFmpeg video decoder error."); + mCallback->Error(); + return; + } + + if (!decoded) { + // The decoder doesn't have enough data to decode a frame yet. + return; + } + + nsAutoPtr data; + + VideoInfo info; + info.mDisplay = nsIntSize(mCodecContext.width, mCodecContext.height); + info.mStereoMode = StereoMode::MONO; + info.mHasVideo = true; + + data = VideoData::CreateFromImage( + info, mImageContainer, aSample->byte_offset, aSample->composition_timestamp, + aSample->duration, mCurrentImage, aSample->is_sync_point, -1, + gfx::IntRect(0, 0, mCodecContext.width, mCodecContext.height)); + + // Insert the frame into the heap for reordering. + mDelayedFrames.Push(data.forget()); + + // Reorder video frames from decode order to presentation order. The minimum + // size of the heap comes from one P frame + |max_b_frames| B frames, which + // is the maximum number of frames in a row which will be out-of-order. + if (mDelayedFrames.Length() > (uint32_t)mCodecContext.max_b_frames + 1) { + VideoData* d = mDelayedFrames.Pop(); + mCallback->Output(d); + } + + if (mTaskQueue->IsEmpty()) { + mCallback->InputExhausted(); + } +} + +static void +PlanarYCbCrDataFromAVFrame(mozilla::layers::PlanarYCbCrData &aData, + AVFrame* aFrame) +{ + aData.mPicX = aData.mPicY = 0; + aData.mPicSize = mozilla::gfx::IntSize(aFrame->width, aFrame->height); + aData.mStereoMode = StereoMode::MONO; + + aData.mYChannel = aFrame->data[0]; + aData.mYStride = aFrame->linesize[0]; + aData.mYSize = aData.mPicSize; + aData.mYSkip = 0; + + aData.mCbChannel = aFrame->data[1]; + aData.mCrChannel = aFrame->data[2]; + aData.mCbCrStride = aFrame->linesize[1]; + aData.mCbSkip = aData.mCrSkip = 0; + aData.mCbCrSize = + mozilla::gfx::IntSize((aFrame->width + 1) / 2, (aFrame->height + 1) / 2); +} + +/* static */ int +FFmpegH264Decoder::AllocateBufferCb(AVCodecContext* aCodecContext, + AVFrame* aFrame) +{ + MOZ_ASSERT(aCodecContext->codec_type == AVMEDIA_TYPE_VIDEO); + + FFmpegH264Decoder* self = + reinterpret_cast(aCodecContext->opaque); + + switch (aCodecContext->pix_fmt) { + case PIX_FMT_YUV420P: + return self->AllocateYUV420PVideoBuffer(aCodecContext, aFrame); + default: + return avcodec_default_get_buffer(aCodecContext, aFrame); + } +} + +int +FFmpegH264Decoder::AllocateYUV420PVideoBuffer(AVCodecContext* aCodecContext, + AVFrame* aFrame) +{ + // Older versions of ffmpeg require that edges be allocated* around* the + // actual image. + int edgeWidth = avcodec_get_edge_width(); + int decodeWidth = aCodecContext->width + edgeWidth * 2; + int decodeHeight = aCodecContext->height + edgeWidth * 2; + + // Align width and height to possibly speed up decode. + int stride_align[AV_NUM_DATA_POINTERS]; + avcodec_align_dimensions2(aCodecContext, &decodeWidth, &decodeHeight, + stride_align); + + // Get strides for each plane. + av_image_fill_linesizes(aFrame->linesize, aCodecContext->pix_fmt, + decodeWidth); + + // Let FFmpeg set up its YUV plane pointers and tell us how much memory we + // need. + // Note that we're passing |nullptr| here as the base address as we haven't + // allocated our image yet. We will adjust |aFrame->data| below. + size_t allocSize = + av_image_fill_pointers(aFrame->data, aCodecContext->pix_fmt, decodeHeight, + nullptr /* base address */, aFrame->linesize); + + nsRefPtr image = + mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR); + PlanarYCbCrImage* ycbcr = reinterpret_cast(image.get()); + uint8_t* buffer = ycbcr->AllocateAndGetNewBuffer(allocSize); + + if (!buffer) { + NS_WARNING("Failed to allocate buffer for FFmpeg video decoding"); + return -1; + } + + // Now that we've allocated our image, we can add its address to the offsets + // set by |av_image_fill_pointers| above. We also have to add |edgeWidth| + // pixels of padding here. + for (uint32_t i = 0; i < AV_NUM_DATA_POINTERS; i++) { + // The C planes are half the resolution of the Y plane, so we need to halve + // the edge width here. + uint32_t planeEdgeWidth = edgeWidth / (i ? 2 : 1); + + // Add buffer offset, plus a horizontal bar |edgeWidth| pixels high at the + // top of the frame, plus |edgeWidth| pixels from the left of the frame. + aFrame->data[i] += reinterpret_cast( + buffer + planeEdgeWidth * aFrame->linesize[i] + planeEdgeWidth); + } + + // Unused, but needs to be non-zero to keep ffmpeg happy. + aFrame->type = GECKO_FRAME_TYPE; + + aFrame->extended_data = aFrame->data; + aFrame->width = aCodecContext->width; + aFrame->height = aCodecContext->height; + + mozilla::layers::PlanarYCbCrData data; + PlanarYCbCrDataFromAVFrame(data, aFrame); + ycbcr->SetDataNoCopy(data); + + mCurrentImage.swap(image); + + return 0; +} + +nsresult +FFmpegH264Decoder::Input(mp4_demuxer::MP4Sample* aSample) +{ + mTaskQueue->Dispatch( + NS_NewRunnableMethodWithArg >( + this, &FFmpegH264Decoder::DecodeFrame, + nsAutoPtr(aSample))); + + return NS_OK; +} + +void +FFmpegH264Decoder::OutputDelayedFrames() +{ + while (!mDelayedFrames.IsEmpty()) { + mCallback->Output(mDelayedFrames.Pop()); + } +} + +nsresult +FFmpegH264Decoder::Drain() +{ + // The maximum number of frames that can be waiting to be decoded is + // max_b_frames + 1: One P frame and max_b_frames B frames. + for (int32_t i = 0; i <= mCodecContext.max_b_frames; i++) { + // An empty frame tells FFmpeg to decode the next delayed frame it has in + // its queue, if it has any. + nsAutoPtr empty(new MP4Sample(0 /* dts */, 0 /* cts */, + 0 /* duration */, 0 /* offset */, + new std::vector(), + mp4_demuxer::kVideo, nullptr, + false)); + + nsresult rv = Input(empty.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + + mTaskQueue->Dispatch( + NS_NewRunnableMethod(this, &FFmpegH264Decoder::OutputDelayedFrames)); + + return NS_OK; +} + +nsresult +FFmpegH264Decoder::Flush() +{ + nsresult rv = FFmpegDataDecoder::Flush(); + // Even if the above fails we may as well clear our frame queue. + mDelayedFrames.Clear(); + return rv; +} + +FFmpegH264Decoder::~FFmpegH264Decoder() { + MOZ_COUNT_DTOR(FFmpegH264Decoder); + MOZ_ASSERT(mDelayedFrames.IsEmpty()); +} + +} // namespace mozilla