From 755f95a3a74e4c657becbb8cd9d0294ba67adaf8 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Fri, 12 Sep 2014 19:51:45 -0700 Subject: [PATCH] win-dshow: Add FFmpeg decoding support --- plugins/win-dshow/CMakeLists.txt | 19 ++- plugins/win-dshow/ffmpeg-decode.c | 258 ++++++++++++++++++++++++++++++ plugins/win-dshow/ffmpeg-decode.h | 69 ++++++++ plugins/win-dshow/win-dshow.cpp | 37 ++++- 4 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 plugins/win-dshow/ffmpeg-decode.c create mode 100644 plugins/win-dshow/ffmpeg-decode.h diff --git a/plugins/win-dshow/CMakeLists.txt b/plugins/win-dshow/CMakeLists.txt index 75364e1ad..22b1cc2e7 100644 --- a/plugins/win-dshow/CMakeLists.txt +++ b/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) diff --git a/plugins/win-dshow/ffmpeg-decode.c b/plugins/win-dshow/ffmpeg-decode.c new file mode 100644 index 000000000..b09670b3c --- /dev/null +++ b/plugins/win-dshow/ffmpeg-decode.c @@ -0,0 +1,258 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 . +******************************************************************************/ + +#include "ffmpeg-decode.h" +#include + +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; +} diff --git a/plugins/win-dshow/ffmpeg-decode.h b/plugins/win-dshow/ffmpeg-decode.h new file mode 100644 index 000000000..aaa4cbba1 --- /dev/null +++ b/plugins/win-dshow/ffmpeg-decode.h @@ -0,0 +1,69 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + 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 . +******************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable : 4244) +#pragma warning(disable : 4204) +#endif + +#include +#include + +#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 diff --git a/plugins/win-dshow/win-dshow.cpp b/plugins/win-dshow/win-dshow.cpp index bf9accce5..e893a30d5 100644 --- a/plugins/win-dshow/win-dshow.cpp +++ b/plugins/win-dshow/win-dshow.cpp @@ -6,6 +6,7 @@ #include #include #include "libdshowcapture/dshowcapture.hpp" +#include "ffmpeg-decode.h" #include #include @@ -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);