| Index: webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
|
| diff --git a/webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc b/webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b5a9c3626e1ff421581674589eba6e54aaaafe91
|
| --- /dev/null
|
| +++ b/webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
|
| @@ -0,0 +1,319 @@
|
| +/*
|
| + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license
|
| + * that can be found in the LICENSE file in the root of the source
|
| + * tree. An additional intellectual property rights grant can be found
|
| + * in the file PATENTS. All contributing project authors may
|
| + * be found in the AUTHORS file in the root of the source tree.
|
| + *
|
| + */
|
| +
|
| +#include "webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +extern "C" {
|
| +#include "third_party/ffmpeg/libavcodec/avcodec.h"
|
| +#include "third_party/ffmpeg/libavformat/avformat.h"
|
| +#include "third_party/ffmpeg/libavutil/imgutils.h"
|
| +} // extern "C"
|
| +
|
| +#include "webrtc/base/checks.h"
|
| +#include "webrtc/base/criticalsection.h"
|
| +#include "webrtc/base/logging.h"
|
| +
|
| +namespace webrtc {
|
| +
|
| +namespace {
|
| +
|
| +static bool ffmpeg_initialized = false;
|
| +static const AVPixelFormat pixel_format = AV_PIX_FMT_YUV420P;
|
| +
|
| +// Called by FFmpeg to do mutex operations if init using InitializeFFmpeg.
|
| +static int LockManagerOperation(void** lock, AVLockOp op)
|
| + EXCLUSIVE_LOCK_FUNCTION() UNLOCK_FUNCTION() {
|
| + switch (op) {
|
| + case AV_LOCK_CREATE:
|
| + *lock = new rtc::CriticalSection();
|
| + return 0;
|
| + case AV_LOCK_OBTAIN:
|
| + static_cast<rtc::CriticalSection*>(*lock)->Enter();
|
| + return 0;
|
| + case AV_LOCK_RELEASE:
|
| + static_cast<rtc::CriticalSection*>(*lock)->Leave();
|
| + return 0;
|
| + case AV_LOCK_DESTROY:
|
| + delete static_cast<rtc::CriticalSection*>(*lock);
|
| + *lock = nullptr;
|
| + return 0;
|
| + }
|
| + return 1;
|
| +}
|
| +
|
| +// TODO(hbos): Only init in webrtc standalone (chromium should init for us).
|
| +// TODO(hbos): Non-racey init
|
| +static bool InitializeFFmpeg() {
|
| + if (!ffmpeg_initialized) {
|
| + if (av_lockmgr_register(LockManagerOperation) < 0) {
|
| + LOG(LS_ERROR) << "av_lockmgr_register failed.";
|
| + return false;
|
| + }
|
| + av_register_all();
|
| + ffmpeg_initialized = true;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +// Called by FFmpeg when it is done with a frame buffer, see AVGetBuffer2.
|
| +static void AVFreeBuffer2(void* opaque, uint8_t* data) {
|
| + VideoFrame* video_frame = static_cast<VideoFrame*>(opaque);
|
| + delete video_frame;
|
| +}
|
| +
|
| +// Called by FFmpeg when it needs a frame buffer to store decoded frames in.
|
| +// The VideoFrames in returned by FFmpeg at Decode originate from here. They are
|
| +// reference counted and freed by FFmpeg using AVFreeBuffer2.
|
| +// TODO(hbos): Use a frame pool for better performance instead of create/free.
|
| +// Could be owned by decoder, static_cast<H264DecoderImpl*>(context->opaque).
|
| +static int AVGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags) {
|
| + RTC_CHECK_EQ(context->pix_fmt, pixel_format); // Same as in InitDecode.
|
| +
|
| + // width/height and coded_width/coded_height can be different due to cropping
|
| + // or |lowres|.
|
| + int width = std::max(context->width, context->coded_width);
|
| + int height = std::max(context->height, context->coded_height);
|
| + // See |lowres|, if used the decoder scales the image by 1/2^(lowres). This
|
| + // has implications on which resolutions are valid, but we don't use it.
|
| + RTC_CHECK_EQ(context->lowres, 0);
|
| +
|
| + RTC_CHECK_GE(width, 0);
|
| + RTC_CHECK_GE(height, 0);
|
| + int ret = av_image_check_size(width, height, 0, nullptr);
|
| + if (ret < 0) {
|
| + LOG(LS_ERROR) << "Invalid picture size " << width << "x" << height;
|
| + return ret;
|
| + }
|
| +
|
| + VideoFrame* video_frame = new VideoFrame();
|
| + int stride_y = width;
|
| + int stride_u = (width + 1) / 2;
|
| + int stride_v = (width + 1) / 2;
|
| + RTC_CHECK_EQ(0, video_frame->CreateEmptyFrame(width, height,
|
| + stride_y, stride_u, stride_v));
|
| + size_t total_size = video_frame->allocated_size(kYPlane) +
|
| + video_frame->allocated_size(kUPlane) +
|
| + video_frame->allocated_size(kVPlane);
|
| + RTC_DCHECK_EQ(total_size, static_cast<size_t>(stride_y * height +
|
| + (stride_u + stride_v) * ((height + 1) / 2)));
|
| + // FFmpeg note: "Each data plane must be aligned to the maximum required by
|
| + // the target CPU." See get_buffer2.
|
| + // TODO(hbos): Memory alignment on a per-plane basis. CreateEmptyFrame only
|
| + // guarantees that the buffer of all planes is memory aligned, not each
|
| + // individual plane.
|
| +
|
| + // FFmpeg expects the initial allocation to be zero-initialized according to
|
| + // http://crbug.com/390941.
|
| + // Expect YUV to be a continuous blob of memory so that we can zero-initialize
|
| + // with a single memset operation instead of three.
|
| + RTC_DCHECK_EQ(video_frame->buffer(kUPlane),
|
| + video_frame->buffer(kYPlane) + video_frame->allocated_size(kYPlane));
|
| + RTC_DCHECK_EQ(video_frame->buffer(kVPlane),
|
| + video_frame->buffer(kUPlane) + video_frame->allocated_size(kUPlane));
|
| + memset(video_frame->buffer(kYPlane), 0, total_size);
|
| +
|
| + frame->width = width;
|
| + frame->height = height;
|
| + frame->format = context->pix_fmt;
|
| + frame->reordered_opaque = context->reordered_opaque;
|
| +
|
| + frame->data[kYPlane] = video_frame->buffer(kYPlane);
|
| + frame->linesize[kYPlane] = video_frame->stride(kYPlane);
|
| + frame->data[kUPlane] = video_frame->buffer(kUPlane);
|
| + frame->linesize[kUPlane] = video_frame->stride(kUPlane);
|
| + frame->data[kVPlane] = video_frame->buffer(kVPlane);
|
| + frame->linesize[kVPlane] = video_frame->stride(kVPlane);
|
| + RTC_DCHECK_EQ(frame->extended_data, frame->data);
|
| +
|
| + frame->buf[0] = av_buffer_create(frame->data[0],
|
| + total_size,
|
| + AVFreeBuffer2,
|
| + static_cast<void*>(video_frame),
|
| + 0);
|
| + RTC_CHECK(frame->buf[0]);
|
| + return 0;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +H264DecoderImpl::H264DecoderImpl()
|
| + : decoded_image_callback_(nullptr) {
|
| +}
|
| +
|
| +H264DecoderImpl::~H264DecoderImpl() {
|
| + Release();
|
| +}
|
| +
|
| +int32_t H264DecoderImpl::InitDecode(const VideoCodec* codec_settings,
|
| + int32_t /*number_of_cores*/) {
|
| + if (codec_settings &&
|
| + codec_settings->codecType != kVideoCodecH264) {
|
| + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
| + }
|
| +
|
| + // Make sure FFmpeg has been initialized.
|
| + InitializeFFmpeg();
|
| +
|
| + // Release necessary in case of re-initializing.
|
| + int32_t ret = Release();
|
| + if (ret != WEBRTC_VIDEO_CODEC_OK)
|
| + return ret;
|
| + RTC_DCHECK(!av_context_);
|
| +
|
| + // Initialize AVCodecContext.
|
| + av_context_.reset(avcodec_alloc_context3(nullptr));
|
| +
|
| + av_context_->codec_type = AVMEDIA_TYPE_VIDEO;
|
| + av_context_->codec_id = AV_CODEC_ID_H264;
|
| + // This is meant to be able to decode OpenH264 streams, which should be
|
| + // baseline profile.
|
| + av_context_->profile = FF_PROFILE_H264_BASELINE;
|
| + if (codec_settings) {
|
| + av_context_->coded_width = codec_settings->width;
|
| + av_context_->coded_height = codec_settings->height;
|
| + }
|
| + av_context_->pix_fmt = pixel_format;
|
| + av_context_->extradata = nullptr;
|
| + av_context_->extradata_size = 0;
|
| +
|
| + av_context_->thread_count = 4;
|
| + av_context_->thread_type = FF_THREAD_SLICE;
|
| +
|
| + // FFmpeg will get video buffers from our AVGetBuffer2, memory managed by us.
|
| + av_context_->get_buffer2 = AVGetBuffer2;
|
| + // get_buffer2 is called with the context, there |opaque| can be used to get a
|
| + // pointer |this|.
|
| + av_context_->opaque = this;
|
| + // Use ref counted frames (av_frame_unref).
|
| + av_context_->refcounted_frames = 1; // "true"
|
| +
|
| + AVCodec* codec = avcodec_find_decoder(av_context_->codec_id);
|
| + if (!codec) {
|
| + // This is an indication that FFmpeg has not been initialized or it has not
|
| + // been compiled/initialized with the correct set of codecs.
|
| + LOG(LS_ERROR) << "FFmpeg H.264 decoder not found.";
|
| + Release();
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + }
|
| + int res = avcodec_open2(av_context_.get(), codec, nullptr);
|
| + if (res < 0) {
|
| + LOG(LS_ERROR) << "avcodec_open2 error: " << res;
|
| + Release();
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + }
|
| +
|
| + av_frame_.reset(av_frame_alloc());
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +int32_t H264DecoderImpl::Release() {
|
| + av_context_.reset();
|
| + av_frame_.reset();
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +int32_t H264DecoderImpl::Reset() {
|
| + if (!IsInitialized())
|
| + return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
| + InitDecode(nullptr, 1);
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +int32_t H264DecoderImpl::RegisterDecodeCompleteCallback(
|
| + DecodedImageCallback* callback) {
|
| + decoded_image_callback_ = callback;
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +int32_t H264DecoderImpl::Decode(const EncodedImage& input_image,
|
| + bool /*missing_frames*/,
|
| + const RTPFragmentationHeader* /*fragmentation*/,
|
| + const CodecSpecificInfo* codec_specific_info,
|
| + int64_t /*render_time_ms*/) {
|
| + if (!IsInitialized())
|
| + return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
| + if (!decoded_image_callback_) {
|
| + LOG(LS_WARNING) << "InitDecode() has been called, but a callback function "
|
| + "has not been set with RegisterDecodeCompleteCallback()";
|
| + return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
| + }
|
| + if (!input_image._buffer || !input_image._length)
|
| + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
| + if (codec_specific_info &&
|
| + codec_specific_info->codecType != kVideoCodecH264) {
|
| + return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
| + }
|
| +
|
| + AVPacket packet;
|
| + av_init_packet(&packet);
|
| + // TODO(hbos): "The input buffer must be AV_INPUT_BUFFER_PADDING_SIZE larger
|
| + // than the actual read bytes because some optimized bitstream readers read 32
|
| + // or 64 bits at once and could read over the end." See avcodec_decode_video2.
|
| + // - Is this an issue? Do we have to make sure EncodedImage is allocated with
|
| + // additional bytes or do we have to do an otherwise unnecessary copy? Might
|
| + // only be a problem with non-mul-16 frame widths?
|
| + // ("If the first 23 bits of the additional bytes are not 0, then damaged MPEG
|
| + // bitstreams could cause overread and segfault.")
|
| + packet.data = input_image._buffer;
|
| + packet.size = input_image._length;
|
| + av_context_->reordered_opaque = input_image.ntp_time_ms_ * 1000; // ms -> μs
|
| +
|
| + int frame_decoded = 0;
|
| + int result = avcodec_decode_video2(av_context_.get(),
|
| + av_frame_.get(),
|
| + &frame_decoded,
|
| + &packet);
|
| + if (result < 0) {
|
| + LOG(LS_ERROR) << "avcodec_decode_video2 error: " << result;
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + }
|
| + // |result| is number of bytes used, which should be all of them.
|
| + if (result != packet.size) {
|
| + LOG(LS_ERROR) << "avcodec_decode_video2 consumed " << result << " bytes "
|
| + "when " << packet.size << " bytes were expected.";
|
| + return WEBRTC_VIDEO_CODEC_ERROR;
|
| + }
|
| +
|
| + if (!frame_decoded) {
|
| + LOG(LS_WARNING) << "avcodec_decode_video2 successful but no frame was "
|
| + "decoded.";
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| + }
|
| +
|
| + // Obtain the |video_frame| containing the decoded image.
|
| + VideoFrame* video_frame = static_cast<VideoFrame*>(
|
| + av_buffer_get_opaque(av_frame_->buf[0]));
|
| + RTC_DCHECK(video_frame);
|
| + RTC_CHECK_EQ(av_frame_->data[kYPlane], video_frame->buffer(kYPlane));
|
| + RTC_CHECK_EQ(av_frame_->data[kUPlane], video_frame->buffer(kUPlane));
|
| + RTC_CHECK_EQ(av_frame_->data[kVPlane], video_frame->buffer(kVPlane));
|
| + video_frame->set_timestamp(input_image._timeStamp);
|
| +
|
| + // Return decoded frame.
|
| + int32_t ret = decoded_image_callback_->Decoded(*video_frame);
|
| + // Stop referencing it, possibly freeing |video_frame|.
|
| + av_frame_unref(av_frame_.get());
|
| + video_frame = nullptr;
|
| +
|
| + if (ret) {
|
| + LOG(LS_WARNING) << "DecodedImageCallback::Decoded returned " << ret;
|
| + return ret;
|
| + }
|
| + return WEBRTC_VIDEO_CODEC_OK;
|
| +}
|
| +
|
| +bool H264DecoderImpl::IsInitialized() const {
|
| + return av_context_;
|
| +}
|
| +
|
| +} // namespace webrtc
|
|
|