Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Unified Diff: webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc

Issue 1306813009: H.264 video codec support using OpenH264/FFmpeg (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: sync_chromium.py SCRIPT_VERSION bumped ensuring ffmpeg is synced Created 4 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..646135268d591cd09a40c18a7d906f89dd7920e9
--- /dev/null
+++ b/webrtc/modules/video_coding/codecs/h264/h264_decoder_impl.cc
@@ -0,0 +1,321 @@
+/*
+ * 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): Assumed to be called on a single thread. Should DCHECK that
+// InitializeFFmpeg is only called on one thread or make it thread safe.
+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 returned by FFmpeg at Decode originate from here. They are
+// reference counted and freed by FFmpeg using AVFreeBuffer2.
noahric 2016/01/04 20:49:30 Is it also true that the AVFrame is basically unus
hbos 2016/01/07 19:41:14 It is used by ffmpeg, we are required to set certa
+// 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) {
noahric 2016/01/04 20:49:30 consider naming this out_frame or av_frame. Normal
hbos 2016/01/07 19:41:14 Done.
+ 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_uv = (width + 1) / 2;
+ RTC_CHECK_EQ(0, video_frame->CreateEmptyFrame(
+ width, height, stride_y, stride_uv, stride_uv));
+ 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_uv + stride_uv) * ((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.
noahric 2016/01/04 20:49:30 Sounds like an issue for i420 frame, then. Is it w
hbos 2016/01/07 19:41:14 I read some FFmpeg documentation a second time and
hbos 2016/01/12 13:56:26 Removing the TODO based on new interpretation of t
+
+ // 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
noahric 2016/01/04 20:49:30 Is it worth it to just do three memsets? It's prob
hbos 2016/01/07 19:41:14 Probably not. But as you pointed out, it looks lik
+ // 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);
noahric 2016/01/04 20:49:30 If the absolute values of these matter (it looks l
hbos 2016/01/07 19:41:14 Done.
+ 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],
noahric 2016/01/04 20:49:30 It looks like this is also requiring that the data
hbos 2016/01/07 19:41:14 Done.
+ 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;
+ }
+
+ // In Chromium FFmpeg will be initialized outside of WebRTC and we should not
+ // attempt to do so ourselves or it will be initialized twice.
+#if !defined(WEBRTC_CHROMIUM_BUILD)
noahric 2016/01/04 20:49:30 Consider putting this behind a different flag, if
hbos 2016/01/07 19:41:14 Good point. Added a TODO, I'll address this in a f
+ // Make sure FFmpeg has been initialized.
+ InitializeFFmpeg();
+#endif
+
+ // 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.
noahric 2016/01/04 20:49:30 I assume it's also meant to be able to decode stre
hbos 2016/01/07 19:41:14 You're right! I looked this up, it should not be s
+ 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;
noahric 2016/01/04 20:49:30 Should this be gleaned from something else? Number
hbos 2016/01/07 19:41:14 Done. Copied from vp8_impl.cc.
+ 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-32 frame widths?
noahric 2016/01/04 20:49:30 I'd buy the second part of this sentence, and it's
hbos 2016/01/07 19:41:14 (I'll address this tomorrow, getting late)
hbos 2016/01/11 16:21:53 Thanks for the info, yeah should be easy. Created
+ // ("If the first 23 bits of the additional bytes are not 0, then damaged MPEG
+ // bitstreams could cause overread and segfault.")
noahric 2016/01/04 20:49:30 I think this could happen anyways today (VCMFrameB
hbos 2016/01/11 16:21:53 Done. (Same bug)
+ packet.data = input_image._buffer;
+ packet.size = input_image._length;
+ av_context_->reordered_opaque = input_image.ntp_time_ms_ * 1000; // ms -> μs
noahric 2016/01/04 20:49:30 Can you add a comment here? avcodec.h says this is
hbos 2016/01/07 19:41:14 Lies! In some versions of ffmpeg it is deprecated,
+
+ 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_;
noahric 2016/01/04 20:49:30 av_context_ != nullptr (implicit cast to bool wil
hbos 2016/01/07 19:41:14 Done.
+}
+
+} // namespace webrtc

Powered by Google App Engine
This is Rietveld 408576698