Bug 941298 - FFmpeg PlatformDecoderModule for Linux r=doublec,cpearce

This commit is contained in:
Edwin Flores 2014-03-21 19:35:15 +13:00
parent 372079241f
commit cab563a65a
18 changed files with 1048 additions and 7 deletions

View File

@ -12,6 +12,9 @@
#ifdef XP_WIN
#include "mozilla/WindowsVersion.h"
#endif
#ifdef MOZ_FFMPEG
#include "FFmpegDecoderModule.h"
#endif
namespace mozilla {
@ -64,17 +67,33 @@ MP4Decoder::GetSupportedCodecs(const nsACString& aType,
return false;
}
static bool
IsFFmpegAvailable()
{
#ifndef MOZ_FFMPEG
return false;
#else
if (!Preferences::GetBool("media.fragmented-mp4.ffmpeg.enabled", false)) {
return false;
}
// If we can link to FFmpeg, then we can almost certainly play H264 and AAC
// with it.
return FFmpegDecoderModule::Link();
#endif
}
static bool
HavePlatformMPEGDecoders()
{
return
Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
return Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
#ifdef XP_WIN
// We have H.264/AAC platform decoders on Windows Vista and up.
IsVistaOrLater() ||
// We have H.264/AAC platform decoders on Windows Vista and up.
IsVistaOrLater() ||
#endif
// TODO: Other platforms...
false;
IsFFmpegAvailable() ||
// TODO: Other platforms...
false;
}
/* static */

View File

@ -8,6 +8,9 @@
#ifdef XP_WIN
#include "WMFDecoderModule.h"
#endif
#ifdef MOZ_FFMPEG
#include "FFmpegDecoderModule.h"
#endif
#include "mozilla/Preferences.h"
namespace mozilla {
@ -15,6 +18,7 @@ namespace mozilla {
extern PlatformDecoderModule* CreateBlankDecoderModule();
bool PlatformDecoderModule::sUseBlankDecoder = false;
bool PlatformDecoderModule::sFFmpegDecoderEnabled = false;
/* static */
void
@ -26,7 +30,12 @@ PlatformDecoderModule::Init()
return;
}
alreadyInitialized = true;
sUseBlankDecoder = Preferences::GetBool("media.fragmented-mp4.use-blank-decoder");
Preferences::AddBoolVarCache(&sUseBlankDecoder,
"media.fragmented-mp4.use-blank-decoder");
Preferences::AddBoolVarCache(&sFFmpegDecoderEnabled,
"media.fragmented-mp4.ffmpeg.enabled", false);
#ifdef XP_WIN
WMFDecoderModule::Init();
#endif
@ -44,6 +53,11 @@ PlatformDecoderModule::Create()
if (NS_SUCCEEDED(m->Startup())) {
return m.forget();
}
#endif
#ifdef MOZ_FFMPEG
if (sFFmpegDecoderEnabled) {
return new FFmpegDecoderModule();
}
#endif
return nullptr;
}

View File

@ -109,6 +109,7 @@ protected:
PlatformDecoderModule() {}
// Caches pref media.fragmented-mp4.use-blank-decoder
static bool sUseBlankDecoder;
static bool sFFmpegDecoderEnabled;
};
// A callback used by MediaDataDecoder to return output/errors to the

View File

@ -0,0 +1,130 @@
/* -*- 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 "FFmpegRuntimeLinker.h"
#include "FFmpegAACDecoder.h"
#define MAX_CHANNELS 16
typedef mp4_demuxer::MP4Sample MP4Sample;
namespace mozilla
{
FFmpegAACDecoder::FFmpegAACDecoder(
MediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
const mp4_demuxer::AudioDecoderConfig &aConfig)
: FFmpegDataDecoder(aTaskQueue, AV_CODEC_ID_AAC)
, mCallback(aCallback)
, mConfig(aConfig)
{
MOZ_COUNT_CTOR(FFmpegAACDecoder);
}
nsresult
FFmpegAACDecoder::Init()
{
nsresult rv = FFmpegDataDecoder::Init();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
static AudioDataValue*
CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumSamples)
{
// These are the only two valid AAC packet sizes.
NS_ASSERTION(aNumSamples == 960 || aNumSamples == 1024,
"Should have exactly one AAC audio packet.");
MOZ_ASSERT(aNumChannels <= MAX_CHANNELS);
nsAutoArrayPtr<AudioDataValue> audio(
new AudioDataValue[aNumChannels * aNumSamples]);
AudioDataValue** data = reinterpret_cast<AudioDataValue**>(aFrame->data);
if (aFrame->format == AV_SAMPLE_FMT_FLT) {
// Audio data already packed. No need to do anything other than copy it
// into a buffer we own.
memcpy(audio, data[0], aNumChannels * aNumSamples * sizeof(AudioDataValue));
} else if (aFrame->format == AV_SAMPLE_FMT_FLTP) {
// Planar audio data. Pack it into something we can understand.
for (uint32_t channel = 0; channel < aNumChannels; channel++) {
for (uint32_t sample = 0; sample < aNumSamples; sample++) {
audio[sample * aNumChannels + channel] = data[channel][sample];
}
}
}
return audio.forget();
}
void
FFmpegAACDecoder::DecodePacket(MP4Sample* aSample)
{
nsAutoPtr<AVFrame> frame(avcodec_alloc_frame());
avcodec_get_frame_defaults(frame);
AVPacket packet;
av_init_packet(&packet);
packet.data = &(*aSample->data)[0];
packet.size = aSample->data->size();
packet.pos = aSample->byte_offset;
packet.dts = aSample->decode_timestamp;
int decoded;
int bytesConsumed =
avcodec_decode_audio4(&mCodecContext, frame.get(), &decoded, &packet);
if (bytesConsumed < 0 || !decoded) {
NS_WARNING("FFmpeg audio decoder error.");
mCallback->Error();
return;
}
NS_ASSERTION(bytesConsumed == (int)aSample->data->size(),
"Only one audio packet should be received at a time.");
uint32_t numChannels = mCodecContext.channels;
nsAutoArrayPtr<AudioDataValue> audio(
CopyAndPackAudio(frame.get(), numChannels, frame->nb_samples));
nsAutoPtr<AudioData> data(new AudioData(packet.pos, aSample->decode_timestamp,
aSample->duration, frame->nb_samples,
audio.forget(), numChannels));
mCallback->Output(data.forget());
if (mTaskQueue->IsEmpty()) {
mCallback->InputExhausted();
}
}
nsresult
FFmpegAACDecoder::Input(MP4Sample* aSample)
{
mTaskQueue->Dispatch(NS_NewRunnableMethodWithArg<nsAutoPtr<MP4Sample> >(
this, &FFmpegAACDecoder::DecodePacket, nsAutoPtr<MP4Sample>(aSample)));
return NS_OK;
}
nsresult
FFmpegAACDecoder::Drain()
{
// AAC is never delayed; nothing to do here.
return NS_OK;
}
FFmpegAACDecoder::~FFmpegAACDecoder() {
MOZ_COUNT_DTOR(FFmpegAACDecoder);
}
} // namespace mozilla

View File

@ -0,0 +1,36 @@
/* -*- 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/. */
#ifndef __FFmpegAACDecoder_h__
#define __FFmpegAACDecoder_h__
#include "FFmpegDataDecoder.h"
namespace mozilla
{
class FFmpegAACDecoder : public FFmpegDataDecoder
{
public:
FFmpegAACDecoder(MediaTaskQueue* aTaskQueue,
MediaDataDecoderCallback* aCallback,
const mp4_demuxer::AudioDecoderConfig &aConfig);
virtual ~FFmpegAACDecoder();
virtual nsresult Init() MOZ_OVERRIDE;
virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
virtual nsresult Drain() MOZ_OVERRIDE;
private:
void DecodePacket(mp4_demuxer::MP4Sample* aSample);
MediaDataDecoderCallback* mCallback;
mp4_demuxer::AudioDecoderConfig mConfig;
};
} // namespace mozilla
#endif // __FFmpegAACDecoder_h__

View File

@ -0,0 +1,18 @@
/* -*- 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/. */
#ifndef __FFmpegCompat_h__
#define __FFmpegCompat_h__
#include <libavcodec/version.h>
#if LIBAVCODEC_VERSION_MAJOR < 55
#define AV_CODEC_ID_H264 CODEC_ID_H264
#define AV_CODEC_ID_AAC CODEC_ID_AAC
typedef CodecID AVCodecID;
#endif
#endif // __FFmpegCompat_h__

View File

@ -0,0 +1,124 @@
/* -*- 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 <string.h>
#include <unistd.h>
#include "MediaTaskQueue.h"
#include "mp4_demuxer/mp4_demuxer.h"
#include "FFmpegRuntimeLinker.h"
#include "FFmpegDataDecoder.h"
namespace mozilla
{
bool FFmpegDataDecoder::sFFmpegInitDone = false;
FFmpegDataDecoder::FFmpegDataDecoder(MediaTaskQueue* aTaskQueue,
AVCodecID aCodecID)
: mTaskQueue(aTaskQueue), mCodecID(aCodecID)
{
MOZ_COUNT_CTOR(FFmpegDataDecoder);
}
FFmpegDataDecoder::~FFmpegDataDecoder() {
MOZ_COUNT_DTOR(FFmpegDataDecoder);
}
/**
* FFmpeg calls back to this function with a list of pixel formats it supports.
* We choose a pixel format that we support and return it.
* For now, we just look for YUV420P as it is the only non-HW accelerated format
* supported by FFmpeg's H264 decoder.
*/
static PixelFormat
ChoosePixelFormat(AVCodecContext* aCodecContext, const PixelFormat* aFormats)
{
FFMPEG_LOG("Choosing FFmpeg pixel format for video decoding.");
for (; *aFormats > -1; aFormats++) {
if (*aFormats == PIX_FMT_YUV420P) {
FFMPEG_LOG("Requesting pixel format YUV420P.");
return PIX_FMT_YUV420P;
}
}
NS_WARNING("FFmpeg does not share any supported pixel formats.");
return PIX_FMT_NONE;
}
nsresult
FFmpegDataDecoder::Init()
{
FFMPEG_LOG("Initialising FFmpeg decoder.");
if (!FFmpegRuntimeLinker::Link()) {
NS_WARNING("Failed to link FFmpeg shared libraries.");
return NS_ERROR_FAILURE;
}
if (!sFFmpegInitDone) {
av_register_all();
#ifdef DEBUG
av_log_set_level(AV_LOG_DEBUG);
#endif
sFFmpegInitDone = true;
}
AVCodec* codec = avcodec_find_decoder(mCodecID);
if (!codec) {
NS_WARNING("Couldn't find ffmpeg decoder");
return NS_ERROR_FAILURE;
}
if (avcodec_get_context_defaults3(&mCodecContext, codec) < 0) {
NS_WARNING("Couldn't init ffmpeg context");
return NS_ERROR_FAILURE;
}
mCodecContext.opaque = this;
// FFmpeg takes this as a suggestion for what format to use for audio samples.
mCodecContext.request_sample_fmt = AV_SAMPLE_FMT_FLT;
// FFmpeg will call back to this to negotiate a video pixel format.
mCodecContext.get_format = ChoosePixelFormat;
AVDictionary* opts = nullptr;
if (avcodec_open2(&mCodecContext, codec, &opts) < 0) {
NS_WARNING("Couldn't initialise ffmpeg decoder");
return NS_ERROR_FAILURE;
}
if (mCodecContext.codec_type == AVMEDIA_TYPE_AUDIO &&
mCodecContext.sample_fmt != AV_SAMPLE_FMT_FLT &&
mCodecContext.sample_fmt != AV_SAMPLE_FMT_FLTP) {
NS_WARNING("FFmpeg AAC decoder outputs unsupported audio format.");
return NS_ERROR_FAILURE;
}
FFMPEG_LOG("FFmpeg init successful.");
return NS_OK;
}
nsresult
FFmpegDataDecoder::Flush()
{
mTaskQueue->Flush();
avcodec_flush_buffers(&mCodecContext);
return NS_OK;
}
nsresult
FFmpegDataDecoder::Shutdown()
{
if (sFFmpegInitDone) {
avcodec_close(&mCodecContext);
}
return NS_OK;
}
} // namespace mozilla

View File

@ -0,0 +1,45 @@
/* -*- 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/. */
#ifndef __FFmpegDataDecoder_h__
#define __FFmpegDataDecoder_h__
#include "mp4_demuxer/mp4_demuxer.h"
#include "FFmpegDecoderModule.h"
#include "FFmpegRuntimeLinker.h"
#include "FFmpegCompat.h"
namespace mozilla
{
class FFmpegDataDecoder : public MediaDataDecoder
{
public:
FFmpegDataDecoder(MediaTaskQueue* aTaskQueue, AVCodecID aCodecID);
virtual ~FFmpegDataDecoder();
static bool Link();
virtual nsresult Init() MOZ_OVERRIDE;
virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) = 0;
virtual nsresult Flush() MOZ_OVERRIDE;
virtual nsresult Drain() = 0;
virtual nsresult Shutdown() MOZ_OVERRIDE;
protected:
MediaTaskQueue* mTaskQueue;
AVCodecContext mCodecContext;
private:
static bool sFFmpegInitDone;
AVCodecID mCodecID;
};
} // namespace mozilla
#endif // __FFmpegDataDecoder_h__

View File

@ -0,0 +1,81 @@
/* -*- 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 "FFmpegRuntimeLinker.h"
#include "FFmpegAACDecoder.h"
#include "FFmpegH264Decoder.h"
#include "FFmpegDecoderModule.h"
namespace mozilla
{
PRLogModuleInfo* GetFFmpegDecoderLog()
{
static PRLogModuleInfo* sFFmpegDecoderLog = nullptr;
if (!sFFmpegDecoderLog) {
sFFmpegDecoderLog = PR_NewLogModule("FFmpegDecoderModule");
}
return sFFmpegDecoderLog;
}
bool FFmpegDecoderModule::sFFmpegLinkDone = false;
FFmpegDecoderModule::FFmpegDecoderModule()
{
MOZ_COUNT_CTOR(FFmpegDecoderModule);
}
FFmpegDecoderModule::~FFmpegDecoderModule() {
MOZ_COUNT_DTOR(FFmpegDecoderModule);
}
bool
FFmpegDecoderModule::Link()
{
if (sFFmpegLinkDone) {
return true;
}
if (!FFmpegRuntimeLinker::Link()) {
NS_WARNING("Failed to link FFmpeg shared libraries.");
return false;
}
sFFmpegLinkDone = true;
return true;
}
nsresult
FFmpegDecoderModule::Shutdown()
{
// Nothing to do here.
return NS_OK;
}
MediaDataDecoder*
FFmpegDecoderModule::CreateH264Decoder(
const mp4_demuxer::VideoDecoderConfig& aConfig,
mozilla::layers::LayersBackend aLayersBackend,
mozilla::layers::ImageContainer* aImageContainer,
MediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback)
{
FFMPEG_LOG("Creating FFmpeg H264 decoder.");
return new FFmpegH264Decoder(aVideoTaskQueue, aCallback, aConfig,
aImageContainer);
}
MediaDataDecoder*
FFmpegDecoderModule::CreateAACDecoder(
const mp4_demuxer::AudioDecoderConfig& aConfig,
MediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback)
{
FFMPEG_LOG("Creating FFmpeg AAC decoder.");
return new FFmpegAACDecoder(aAudioTaskQueue, aCallback, aConfig);
}
} // namespace mozilla

View File

@ -0,0 +1,50 @@
/* -*- 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/. */
#ifndef __FFmpegDecoderModule_h__
#define __FFmpegDecoderModule_h__
#include "PlatformDecoderModule.h"
namespace mozilla
{
#ifdef PR_LOGGING
extern PRLogModuleInfo* GetFFmpegDecoderLog();
#define FFMPEG_LOG(...) PR_LOG(GetFFmpegDecoderLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#define FFMPEG_LOG(...)
#endif
class FFmpegDecoderModule : public PlatformDecoderModule
{
public:
FFmpegDecoderModule();
virtual ~FFmpegDecoderModule();
static bool Link();
virtual nsresult Shutdown() MOZ_OVERRIDE;
virtual MediaDataDecoder* CreateH264Decoder(
const mp4_demuxer::VideoDecoderConfig& aConfig,
mozilla::layers::LayersBackend aLayersBackend,
mozilla::layers::ImageContainer* aImageContainer,
MediaTaskQueue* aVideoTaskQueue,
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
virtual MediaDataDecoder* CreateAACDecoder(
const mp4_demuxer::AudioDecoderConfig& aConfig,
MediaTaskQueue* aAudioTaskQueue,
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
private:
static bool sFFmpegLinkDone;
};
} // namespace mozilla
#endif // __FFmpegDecoderModule_h__

View File

@ -0,0 +1,25 @@
/* 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/. */
AV_FUNC(LIBAVCODEC, avcodec_align_dimensions2)
AV_FUNC(LIBAVCODEC, avcodec_alloc_frame)
AV_FUNC(LIBAVCODEC, avcodec_close)
AV_FUNC(LIBAVCODEC, avcodec_decode_audio4)
AV_FUNC(LIBAVCODEC, avcodec_decode_video2)
AV_FUNC(LIBAVCODEC, avcodec_default_get_buffer)
AV_FUNC(LIBAVCODEC, avcodec_default_release_buffer)
AV_FUNC(LIBAVCODEC, avcodec_find_decoder)
AV_FUNC(LIBAVCODEC, avcodec_flush_buffers)
AV_FUNC(LIBAVCODEC, avcodec_get_context_defaults3)
AV_FUNC(LIBAVCODEC, avcodec_get_edge_width)
AV_FUNC(LIBAVCODEC, avcodec_get_frame_defaults)
AV_FUNC(LIBAVCODEC, avcodec_open2)
AV_FUNC(LIBAVCODEC, av_init_packet)
AV_FUNC(LIBAVCODEC, av_dict_get)
AV_FUNC(LIBAVFORMAT, av_register_all)
AV_FUNC(LIBAVUTIL, av_image_fill_linesizes)
AV_FUNC(LIBAVUTIL, av_image_fill_pointers)
AV_FUNC(LIBAVUTIL, av_log_set_level)

View File

@ -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<AVFrame> 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<VideoData> 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<FFmpegH264Decoder*>(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> image =
mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
PlanarYCbCrImage* ycbcr = reinterpret_cast<PlanarYCbCrImage*>(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<ptrdiff_t>(
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<nsAutoPtr<mp4_demuxer::MP4Sample> >(
this, &FFmpegH264Decoder::DecodeFrame,
nsAutoPtr<mp4_demuxer::MP4Sample>(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<MP4Sample> empty(new MP4Sample(0 /* dts */, 0 /* cts */,
0 /* duration */, 0 /* offset */,
new std::vector<uint8_t>(),
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

View File

@ -0,0 +1,84 @@
/* -*- 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/. */
#ifndef __FFmpegH264Decoder_h__
#define __FFmpegH264Decoder_h__
#include "nsTPriorityQueue.h"
#include "FFmpegDataDecoder.h"
namespace mozilla
{
class FFmpegH264Decoder : public FFmpegDataDecoder
{
typedef mozilla::layers::Image Image;
typedef mozilla::layers::ImageContainer ImageContainer;
public:
FFmpegH264Decoder(MediaTaskQueue* aTaskQueue,
MediaDataDecoderCallback* aCallback,
const mp4_demuxer::VideoDecoderConfig &aConfig,
ImageContainer* aImageContainer);
virtual ~FFmpegH264Decoder();
virtual nsresult Init() MOZ_OVERRIDE;
virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
virtual nsresult Drain() MOZ_OVERRIDE;
virtual nsresult Flush() MOZ_OVERRIDE;
private:
void DecodeFrame(mp4_demuxer::MP4Sample* aSample);
void OutputDelayedFrames();
/**
* This method allocates a buffer for FFmpeg's decoder, wrapped in an Image.
* Currently it only supports Planar YUV420, which appears to be the only
* non-hardware accelerated image format that FFmpeg's H264 decoder is
* capable of outputting.
*/
int AllocateYUV420PVideoBuffer(AVCodecContext* aCodecContext,
AVFrame* aFrame);
static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
mp4_demuxer::VideoDecoderConfig mConfig;
MediaDataDecoderCallback* mCallback;
nsRefPtr<ImageContainer> mImageContainer;
/**
* Pass Image back from the allocator to our DoDecode method.
* We *should* return the image back through ffmpeg wrapped in an AVFrame like
* we're meant to. However, if avcodec_decode_video2 fails, it returns a
* completely different frame from the one holding our image and it will be
* leaked.
* This could be handled in the future by wrapping our Image in a reference
* counted AVBuffer and letting ffmpeg hold the nsAutoPtr<Image>, but
* currently we have to support older versions of ffmpeg which lack
* refcounting.
*/
nsRefPtr<Image> mCurrentImage;
struct VideoDataComparator
{
bool LessThan(VideoData* const &a, VideoData* const &b) const
{
return a->mTime < b->mTime;
}
};
/**
* FFmpeg returns frames in DTS order, so we need to keep a heap of the
* previous MAX_B_FRAMES + 1 frames (all B frames in a GOP + one P frame)
* ordered by PTS to make sure we present video frames in the intended order.
*/
nsTPriorityQueue<VideoData*, VideoDataComparator> mDelayedFrames;
};
} // namespace mozilla
#endif // __FFmpegH264Decoder_h__

View File

@ -0,0 +1,85 @@
/* -*- 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 <dlfcn.h>
#include "nsDebug.h"
#include "FFmpegRuntimeLinker.h"
// For FFMPEG_LOG
#include "FFmpegDecoderModule.h"
#define NUM_ELEMENTS(X) (sizeof(X) / sizeof((X)[0]))
#define LIBAVCODEC 0
#define LIBAVFORMAT 1
#define LIBAVUTIL 2
namespace mozilla
{
FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus =
LinkStatus_INIT;
static const char * const sLibNames[] = {
"libavcodec.so.53", "libavformat.so.53", "libavutil.so.51",
};
void* FFmpegRuntimeLinker::sLinkedLibs[NUM_ELEMENTS(sLibNames)] = {
nullptr, nullptr, nullptr
};
#define AV_FUNC(lib, func) typeof(func) func;
#include "FFmpegFunctionList.h"
#undef AV_FUNC
/* static */ bool
FFmpegRuntimeLinker::Link()
{
if (sLinkStatus) {
return sLinkStatus == LinkStatus_SUCCEEDED;
}
for (uint32_t i = 0; i < NUM_ELEMENTS(sLinkedLibs); i++) {
if (!(sLinkedLibs[i] = dlopen(sLibNames[i], RTLD_NOW | RTLD_LOCAL))) {
NS_WARNING("Couldn't link ffmpeg libraries.");
goto fail;
}
}
#define AV_FUNC(lib, func) \
func = (typeof(func))dlsym(sLinkedLibs[lib], #func); \
if (!func) { \
NS_WARNING("Couldn't load FFmpeg function " #func "."); \
goto fail; \
}
#include "FFmpegFunctionList.h"
#undef AV_FUNC
sLinkStatus = LinkStatus_SUCCEEDED;
return true;
fail:
Unlink();
sLinkStatus = LinkStatus_FAILED;
return false;
}
/* static */ void
FFmpegRuntimeLinker::Unlink()
{
FFMPEG_LOG("Unlinking ffmpeg libraries.");
for (uint32_t i = 0; i < NUM_ELEMENTS(sLinkedLibs); i++) {
if (sLinkedLibs[i]) {
dlclose(sLinkedLibs[i]);
sLinkedLibs[i] = nullptr;
}
}
}
} // namespace mozilla

View File

@ -0,0 +1,44 @@
/* -*- 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/. */
#ifndef __FFmpegRuntimeLinker_h__
#define __FFmpegRuntimeLinker_h__
extern "C" {
#pragma GCC visibility push(default)
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#pragma GCC visibility pop
}
#include "nsAutoPtr.h"
namespace mozilla
{
class FFmpegRuntimeLinker
{
public:
static bool Link();
static void Unlink();
private:
static void* sLinkedLibs[];
static enum LinkStatus {
LinkStatus_INIT = 0,
LinkStatus_FAILED,
LinkStatus_SUCCEEDED
} sLinkStatus;
};
#define AV_FUNC(lib, func) extern typeof(func)* func;
#include "FFmpegFunctionList.h"
#undef AV_FUNC
}
#endif // __FFmpegRuntimeLinker_h__

View File

@ -129,4 +129,9 @@ if CONFIG['MOZ_WEBSPEECH']:
'/content/media/webspeech/synth',
]
if CONFIG['MOZ_FFMPEG']:
LOCAL_INCLUDES += [
'/content/media/fmp4/ffmpeg/include',
]
FINAL_LIBRARY = 'xul'

View File

@ -96,6 +96,10 @@
#include "GStreamerFormatHelper.h"
#endif
#ifdef MOZ_FFMPEG
#include "FFmpegRuntimeLinker.h"
#endif
#include "AudioStream.h"
#include "Latency.h"
#include "WebAudioUtils.h"
@ -363,6 +367,10 @@ nsLayoutStatics::Shutdown()
GStreamerFormatHelper::Shutdown();
#endif
#ifdef MOZ_FFMPEG
FFmpegRuntimeLinker::Unlink();
#endif
AudioStream::ShutdownLibrary();
AsyncLatencyLogger::ShutdownLogger();
WebAudioUtils::Shutdown();

View File

@ -196,6 +196,7 @@ pref("media.directshow.enabled", true);
#endif
#ifdef MOZ_FMP4
pref("media.fragmented-mp4.enabled", true);
pref("media.fragmented-mp4.ffmpeg.enabled", false);
// Denotes that the fragmented MP4 parser can be created by <video> elements.
// This is for testing, since the parser can't yet handle non-fragmented MP4,
// so it will fail to play most MP4 files.