Browse Source

win-dshow: Add FFmpeg decoding support

jp9000 11 years ago
parent
commit
755f95a3a7

+ 17 - 2
plugins/win-dshow/CMakeLists.txt

@@ -1,7 +1,19 @@
 project(win-dshow)
 
+find_package(Libavutil REQUIRED)
+include_directories(${LIBAVUTIL_INCLUDE_DIRS})
+add_definitions(${LIBAVUTIL_DEFINITIONS})
+
+find_package(Libavcodec REQUIRED)
+include_directories(${LIBAVCODEC_INCLUDE_DIRS})
+add_definitions(${LIBAVCODEC_DEFINITIONS})
+
+set(win-dshow_HEADERS
+	ffmpeg-decode.h)
+
 set(win-dshow_SOURCES
-	win-dshow.cpp)
+	win-dshow.cpp
+	ffmpeg-decode.c)
 
 set(libdshowcapture_SOURCES
 	libdshowcapture/source/capture-filter.cpp
@@ -33,10 +45,13 @@ set(libdshowcapture_HEADERS
 
 add_library(win-dshow MODULE
 	${win-dshow_SOURCES}
+	${win-dshow_HEADERS}
 	${libdshowcapture_SOURCES}
 	${libdshowcapture_HEADERS})
 target_link_libraries(win-dshow
 	libobs
-	strmiids.lib)
+	strmiids.lib
+	${LIBAVUTIL_LIBRARIES}
+	${LIBAVCODEC_LIBRARIES})
 
 install_obs_plugin_with_data(win-dshow data)

+ 258 - 0
plugins/win-dshow/ffmpeg-decode.c

@@ -0,0 +1,258 @@
+/******************************************************************************
+    Copyright (C) 2014 by Hugh Bailey <[email protected]>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#include "ffmpeg-decode.h"
+#include <obs-avc.h>
+
+int ffmpeg_decode_init(struct ffmpeg_decode *decode, enum AVCodecID id)
+{
+	int ret;
+
+	avcodec_register_all();
+	memset(decode, 0, sizeof(*decode));
+
+	decode->codec = avcodec_find_decoder(id);
+	if (!decode->codec)
+		return -1;
+
+	decode->decoder = avcodec_alloc_context3(decode->codec);
+
+	ret = avcodec_open2(decode->decoder, decode->codec, NULL);
+	if (ret < 0) {
+		ffmpeg_decode_free(decode);
+		return ret;
+	}
+
+	if (decode->codec->capabilities & CODEC_CAP_TRUNCATED)
+		decode->decoder->flags |= CODEC_FLAG_TRUNCATED;
+
+	return 0;
+}
+
+void ffmpeg_decode_free(struct ffmpeg_decode *decode)
+{
+	if (decode->decoder) {
+		avcodec_close(decode->decoder);
+		av_free(decode->decoder);
+	}
+
+	if (decode->frame)
+		av_free(decode->frame);
+
+	if (decode->packet_buffer)
+		bfree(decode->packet_buffer);
+
+	memset(decode, 0, sizeof(*decode));
+}
+
+static inline enum video_format convert_pixel_format(int f)
+{
+	switch (f) {
+	case AV_PIX_FMT_NONE:    return VIDEO_FORMAT_NONE;
+	case AV_PIX_FMT_YUV420P: return VIDEO_FORMAT_I420;
+	case AV_PIX_FMT_NV12:    return VIDEO_FORMAT_NV12;
+	case AV_PIX_FMT_YUYV422: return VIDEO_FORMAT_YUY2;
+	case AV_PIX_FMT_UYVY422: return VIDEO_FORMAT_UYVY;
+	case AV_PIX_FMT_RGBA:    return VIDEO_FORMAT_RGBA;
+	case AV_PIX_FMT_BGRA:    return VIDEO_FORMAT_BGRA;
+	case AV_PIX_FMT_BGR0:    return VIDEO_FORMAT_BGRX;
+	default:;
+	}
+
+	return VIDEO_FORMAT_NONE;
+}
+
+static inline enum audio_format convert_sample_format(int f)
+{
+	switch (f) {
+	case AV_SAMPLE_FMT_U8:   return AUDIO_FORMAT_U8BIT;
+	case AV_SAMPLE_FMT_S16:  return AUDIO_FORMAT_16BIT;
+	case AV_SAMPLE_FMT_S32:  return AUDIO_FORMAT_32BIT;
+	case AV_SAMPLE_FMT_FLT:  return AUDIO_FORMAT_FLOAT;
+	case AV_SAMPLE_FMT_U8P:  return AUDIO_FORMAT_U8BIT_PLANAR;
+	case AV_SAMPLE_FMT_S16P: return AUDIO_FORMAT_16BIT_PLANAR;
+	case AV_SAMPLE_FMT_S32P: return AUDIO_FORMAT_32BIT_PLANAR;
+	case AV_SAMPLE_FMT_FLTP: return AUDIO_FORMAT_FLOAT_PLANAR;
+	default:;
+	}
+
+	return AUDIO_FORMAT_UNKNOWN;
+}
+
+static inline void copy_data(struct ffmpeg_decode *decode, uint8_t *data,
+		size_t size)
+{
+	size_t new_size = size + FF_INPUT_BUFFER_PADDING_SIZE;
+
+	if (decode->packet_size < new_size) {
+		decode->packet_buffer = brealloc(decode->packet_buffer,
+				new_size);
+	}
+
+	memset(decode->packet_buffer + size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
+	memcpy(decode->packet_buffer, data, size);
+}
+
+int ffmpeg_decode_audio(struct ffmpeg_decode *decode,
+		uint8_t *data, size_t size,
+		struct obs_source_audio *audio,
+		bool *got_output)
+{
+	AVPacket packet = {0};
+	int got_frame = false;
+	int len;
+
+	*got_output = false;
+
+	copy_data(decode, data, size);
+
+	av_init_packet(&packet);
+	packet.data = decode->packet_buffer;
+	packet.size = size;
+
+	if (!decode->frame) {
+		decode->frame = avcodec_alloc_frame();
+		if (!decode->frame)
+			return -1;
+	} else {
+		avcodec_get_frame_defaults(decode->frame);
+	}
+
+	len = avcodec_decode_audio4(decode->decoder, decode->frame, &got_frame,
+			&packet);
+
+	if (len <= 0 || !got_frame)
+		return len;
+
+	for (size_t i = 0; i < MAX_AV_PLANES; i++)
+		audio->data[i] = decode->frame->data[i];
+
+	audio->samples_per_sec = decode->frame->sample_rate;
+	audio->speakers        = (enum speaker_layout)decode->decoder->channels;
+	audio->format          = convert_sample_format(decode->frame->format);
+
+	audio->frames = decode->frame->nb_samples;
+
+	if (audio->format == AUDIO_FORMAT_UNKNOWN)
+		return 0;
+
+	*got_output = true;
+	return len;
+}
+
+#define NAL_SLICE     1
+#define NAL_SLICE_IDR 5
+
+static bool avc_keyframe(const uint8_t *data, size_t size)
+{
+	const uint8_t *nal_start, *nal_end;
+	const uint8_t *end = data + size;
+	int type;
+
+	nal_start = obs_avc_find_startcode(data, end);
+	while (true) {
+		while (nal_start < end && !*(nal_start++));
+
+		if (nal_start == end)
+			break;
+
+		type = nal_start[0] & 0x1F;
+
+		if (type == NAL_SLICE_IDR || type == NAL_SLICE)
+			return (type == NAL_SLICE_IDR);
+
+		nal_end = obs_avc_find_startcode(nal_start, end);
+		nal_start = nal_end;
+	}
+
+	return false;
+}
+
+int ffmpeg_decode_video(struct ffmpeg_decode *decode,
+		uint8_t *data, size_t size, long long *ts,
+		struct obs_source_frame *frame,
+		bool *got_output)
+{
+	AVPacket packet = {0};
+	int got_frame = false;
+	enum video_format new_format;
+	int len;
+
+	*got_output = false;
+
+	copy_data(decode, data, size);
+
+	av_init_packet(&packet);
+	packet.data     = decode->packet_buffer;
+	packet.size     = size;
+	packet.pts      = *ts;
+
+	if (decode->codec->id == AV_CODEC_ID_H264 && avc_keyframe(data, size))
+		packet.flags |= AV_PKT_FLAG_KEY;
+
+	if (!decode->frame) {
+		decode->frame = avcodec_alloc_frame();
+		if (!decode->frame)
+			return -1;
+	}
+
+	len = avcodec_decode_video2(decode->decoder, decode->frame, &got_frame,
+			&packet);
+
+	if (len <= 0 || !got_frame)
+		return len;
+
+	for (size_t i = 0; i < MAX_AV_PLANES; i++) {
+		frame->data[i]     = decode->frame->data[i];
+		frame->linesize[i] = decode->frame->linesize[i];
+	}
+
+	new_format = convert_pixel_format(decode->frame->format);
+	if (new_format != frame->format) {
+		bool success;
+		enum video_range_type range;
+
+		frame->format = new_format;
+		frame->full_range =
+			decode->frame->color_range == AVCOL_RANGE_JPEG;
+
+		range = frame->full_range ?
+			VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
+
+		success = video_format_get_parameters(VIDEO_CS_601,
+				range, frame->color_matrix,
+				frame->color_range_min, frame->color_range_max);
+		if (!success) {
+			blog(LOG_ERROR, "Failed to get video format "
+			                "parameters for video format %u",
+			                VIDEO_CS_601);
+			return 0;
+		}
+	}
+
+	*ts = decode->frame->pkt_pts;
+
+	frame->width  = decode->frame->width;
+	frame->height = decode->frame->height;
+	frame->flip   = false;
+
+	if (frame->format == VIDEO_FORMAT_NONE)
+		return 0;
+
+	*got_output = true;
+	return len;
+}

+ 69 - 0
plugins/win-dshow/ffmpeg-decode.h

@@ -0,0 +1,69 @@
+/******************************************************************************
+    Copyright (C) 2014 by Hugh Bailey <[email protected]>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+******************************************************************************/
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <obs.h>
+
+#ifdef _WIN32
+#pragma warning(push)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4204)
+#endif
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/log.h>
+
+#ifdef _WIN32
+#pragma warning(pop)
+#endif
+
+struct ffmpeg_decode {
+	AVCodecContext *decoder;
+	AVCodec        *codec;
+
+	AVFrame        *frame;
+
+	uint8_t        *packet_buffer;
+	size_t         packet_size;
+};
+
+extern int ffmpeg_decode_init(struct ffmpeg_decode *decode, enum AVCodecID id);
+extern void ffmpeg_decode_free(struct ffmpeg_decode *decode);
+
+extern int ffmpeg_decode_audio(struct ffmpeg_decode *decode,
+		uint8_t *data, size_t size,
+		struct obs_source_audio *audio,
+		bool *got_output);
+
+extern int ffmpeg_decode_video(struct ffmpeg_decode *decode,
+		uint8_t *data, size_t size, long long *ts,
+		struct obs_source_frame *frame,
+		bool *got_output);
+
+static inline bool ffmpeg_decode_valid(struct ffmpeg_decode *decode)
+{
+	return decode->decoder != NULL;
+}
+
+#ifdef __cplusplus
+}
+#endif

+ 36 - 1
plugins/win-dshow/win-dshow.cpp

@@ -6,6 +6,7 @@
 #include <util/util.hpp>
 #include <util/platform.h>
 #include "libdshowcapture/dshowcapture.hpp"
+#include "ffmpeg-decode.h"
 
 #include <algorithm>
 #include <limits>
@@ -56,6 +57,36 @@ enum ResType {
 	ResType_Custom
 };
 
+void ffmpeg_log(void *bla, int level, const char *msg, va_list args)
+{
+	DStr str;
+	if (level == AV_LOG_WARNING)
+		dstr_copy(str, "warning: ");
+	else if (level == AV_LOG_ERROR)
+		dstr_copy(str, "error:   ");
+	else if (level < AV_LOG_ERROR)
+		dstr_copy(str, "fatal:   ");
+	else
+		return;
+
+	dstr_cat(str, msg);
+	if (dstr_end(str) == '\n')
+		dstr_resize(str, str->len - 1);
+
+	blogva(LOG_WARNING, str, args);
+	av_log_default_callback(bla, level, msg, args);
+}
+
+class Decoder {
+	struct ffmpeg_decode decode;
+
+public:
+	inline Decoder()  {memset(&decode, 0, sizeof(decode));}
+	inline ~Decoder() {ffmpeg_decode_free(&decode);}
+
+	inline operator ffmpeg_decode*() {return &decode;}
+};
+
 struct DShowInput {
 	obs_source_t source;
 	Device       device;
@@ -72,7 +103,11 @@ struct DShowInput {
 		: source         (source_),
 		  device         (InitGraph::False),
 		  comInitialized (false)
-	{}
+	{
+		av_log_set_level(AV_LOG_WARNING);
+		av_log_set_callback(ffmpeg_log);
+	}
+
 
 	void OnVideoData(unsigned char *data, size_t size,
 			long long startTime, long long endTime);