obs-ffmpeg: Change from libff to media-playback

master
jp9000 2017-03-26 06:21:59 -07:00
parent 2329c71dac
commit 45d029b1fb
3 changed files with 92 additions and 411 deletions

View File

@ -26,7 +26,7 @@ add_library(obs-ffmpeg MODULE
${obs-ffmpeg_SOURCES})
target_link_libraries(obs-ffmpeg
libobs
libff
media-playback
${obs-ffmpeg_PLATFORM_DEPS}
${FFMPEG_LIBRARIES})

View File

@ -23,21 +23,12 @@ LocalFile="Local File"
Looping="Loop"
Input="Input"
InputFormat="Input Format"
ForceFormat="Force format conversion"
HardwareDecode="Use hardware decoding when available"
ClearOnMediaEnd="Hide source when playback ends"
Advanced="Advanced"
AudioBufferSize="Audio Buffer Size (frames)"
VideoBufferSize="Video Buffer Size (frames)"
FrameDropping="Frame Dropping Level"
DiscardNone="None"
DiscardDefault="Default (Invalid Packets)"
DiscardNonRef="Non-Reference Frames"
DiscardBiDir="Bi-Directional Frames"
DiscardNonIntra="Non-Intra Frames"
DiscardNonKey="Non-Key Frames"
DiscardAll="All Frames (Careful!)"
RestartWhenActivated="Restart playback when source becomes active"
CloseFileWhenInactive="Close file when inactive"
CloseFileWhenInactive.ToolTip="Closes the file when the source is not being displayed on the stream or\nrecording. This allows the file to be changed when the source isn't active,\nbut there may be some startup delay when the source reactivates."
ColorRange="YUV Color Range"
ColorRange.Auto="Auto"
ColorRange.Partial="Partial"

View File

@ -21,9 +21,7 @@
#include "obs-ffmpeg-compat.h"
#include "obs-ffmpeg-formats.h"
#include <libff/ff-demuxer.h>
#include <libswscale/swscale.h>
#include <media-playback/media.h>
#define FF_LOG(level, format, ...) \
blog(level, "[Media Source]: " format, ##__VA_ARGS__)
@ -37,260 +35,28 @@ static bool video_frame(struct ff_frame *frame, void *opaque);
static bool video_format(AVCodecContext *codec_context, void *opaque);
struct ffmpeg_source {
struct ff_demuxer *demuxer;
mp_media_t media;
bool media_valid;
bool destroy_media;
struct SwsContext *sws_ctx;
int sws_width;
int sws_height;
enum AVPixelFormat sws_format;
uint8_t *sws_data;
int sws_linesize;
enum video_range range;
obs_source_t *source;
char *input;
char *input_format;
enum AVDiscard frame_drop;
enum video_range_type range;
int audio_buffer_size;
int video_buffer_size;
bool is_advanced;
bool is_looping;
bool is_forcing_scale;
bool is_hw_decoding;
bool is_clear_on_media_end;
bool restart_on_activate;
bool close_when_inactive;
};
static bool set_obs_frame_colorprops(struct ff_frame *frame,
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
{
enum AVColorSpace frame_cs = av_frame_get_colorspace(frame->frame);
enum video_colorspace obs_cs;
switch(frame_cs) {
case AVCOL_SPC_BT709: obs_cs = VIDEO_CS_709; break;
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_BT470BG: obs_cs = VIDEO_CS_601; break;
case AVCOL_SPC_UNSPECIFIED: obs_cs = VIDEO_CS_DEFAULT; break;
default:
FF_BLOG(LOG_WARNING, "frame using an unsupported colorspace %d",
frame_cs);
obs_cs = VIDEO_CS_DEFAULT;
}
enum video_range_type range;
obs_frame->format = ffmpeg_to_obs_video_format(frame->frame->format);
obs_frame->full_range =
frame->frame->color_range == AVCOL_RANGE_JPEG;
if (s->range != VIDEO_RANGE_DEFAULT)
obs_frame->full_range = s->range == VIDEO_RANGE_FULL;
range = obs_frame->full_range ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
if (!video_format_get_parameters(obs_cs,
range, obs_frame->color_matrix,
obs_frame->color_range_min,
obs_frame->color_range_max)) {
FF_BLOG(LOG_ERROR, "Failed to get video format "
"parameters for video format %u",
obs_cs);
return false;
}
return true;
}
bool update_sws_context(struct ffmpeg_source *s, AVFrame *frame)
{
if (frame->width != s->sws_width
|| frame->height != s->sws_height
|| frame->format != s->sws_format) {
if (s->sws_ctx != NULL)
sws_freeContext(s->sws_ctx);
if (frame->width <= 0 || frame->height <= 0) {
FF_BLOG(LOG_ERROR, "unable to create a sws "
"context that has a width(%d) or "
"height(%d) of zero.", frame->width,
frame->height);
goto fail;
}
s->sws_ctx = sws_getContext(
frame->width,
frame->height,
frame->format,
frame->width,
frame->height,
AV_PIX_FMT_BGRA,
SWS_BILINEAR,
NULL, NULL, NULL);
if (s->sws_ctx == NULL) {
FF_BLOG(LOG_ERROR, "unable to create sws "
"context with src{w:%d,h:%d,f:%d}->"
"dst{w:%d,h:%d,f:%d}",
frame->width, frame->height,
frame->format, frame->width,
frame->height, AV_PIX_FMT_BGRA);
goto fail;
}
if (s->sws_data != NULL)
bfree(s->sws_data);
s->sws_data = bzalloc(frame->width * frame->height * 4);
if (s->sws_data == NULL) {
FF_BLOG(LOG_ERROR, "unable to allocate sws "
"pixel data with size %d",
frame->width * frame->height * 4);
goto fail;
}
s->sws_linesize = frame->width * 4;
s->sws_width = frame->width;
s->sws_height = frame->height;
s->sws_format = frame->format;
}
return true;
fail:
if (s->sws_ctx != NULL)
sws_freeContext(s->sws_ctx);
s->sws_ctx = NULL;
if (s->sws_data)
bfree(s->sws_data);
s->sws_data = NULL;
s->sws_linesize = 0;
s->sws_width = 0;
s->sws_height = 0;
s->sws_format = 0;
return false;
}
static bool video_frame_scale(struct ff_frame *frame,
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
{
if (!update_sws_context(s, frame->frame))
return false;
sws_scale(
s->sws_ctx,
(uint8_t const *const *)frame->frame->data,
frame->frame->linesize,
0,
frame->frame->height,
&s->sws_data,
&s->sws_linesize
);
obs_frame->data[0] = s->sws_data;
obs_frame->linesize[0] = s->sws_linesize;
obs_frame->format = VIDEO_FORMAT_BGRA;
obs_source_output_video(s->source, obs_frame);
return true;
}
static bool video_frame_hwaccel(struct ff_frame *frame,
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
{
// 4th plane is pixelbuf reference for mac
for (int i = 0; i < 3; i++) {
obs_frame->data[i] = frame->frame->data[i];
obs_frame->linesize[i] = frame->frame->linesize[i];
}
if (!set_obs_frame_colorprops(frame, s, obs_frame))
return false;
obs_source_output_video(s->source, obs_frame);
return true;
}
static bool video_frame_direct(struct ff_frame *frame,
struct ffmpeg_source *s, struct obs_source_frame *obs_frame)
{
int i;
for (i = 0; i < MAX_AV_PLANES; i++) {
obs_frame->data[i] = frame->frame->data[i];
obs_frame->linesize[i] = frame->frame->linesize[i];
}
if (!set_obs_frame_colorprops(frame, s, obs_frame))
return false;
obs_source_output_video(s->source, obs_frame);
return true;
}
static bool video_frame(struct ff_frame *frame, void *opaque)
{
struct ffmpeg_source *s = opaque;
struct obs_source_frame obs_frame = {0};
uint64_t pts;
// Media ended
if (frame == NULL) {
if (s->is_clear_on_media_end)
obs_source_output_video(s->source, NULL);
return true;
}
pts = (uint64_t)(frame->pts * 1000000000.0L);
obs_frame.timestamp = pts;
obs_frame.width = frame->frame->width;
obs_frame.height = frame->frame->height;
enum video_format format =
ffmpeg_to_obs_video_format(frame->frame->format);
if (s->is_forcing_scale || format == VIDEO_FORMAT_NONE)
return video_frame_scale(frame, s, &obs_frame);
else if (s->is_hw_decoding)
return video_frame_hwaccel(frame, s, &obs_frame);
else
return video_frame_direct(frame, s, &obs_frame);
}
static bool audio_frame(struct ff_frame *frame, void *opaque)
{
struct ffmpeg_source *s = opaque;
struct obs_source_audio audio_data = {0};
uint64_t pts;
// Media ended
if (frame == NULL || frame->frame == NULL)
return true;
pts = (uint64_t)(frame->pts * 1000000000.0L);
int channels = av_frame_get_channels(frame->frame);
for(int i = 0; i < channels; i++)
audio_data.data[i] = frame->frame->data[i];
audio_data.samples_per_sec = frame->frame->sample_rate;
audio_data.frames = frame->frame->nb_samples;
audio_data.timestamp = pts;
audio_data.format =
convert_ffmpeg_sample_format(frame->frame->format);
audio_data.speakers = channels;
obs_source_output_audio(s->source, &audio_data);
return true;
}
static bool is_local_file_modified(obs_properties_t *props,
obs_property_t *prop, obs_data_t *settings)
{
@ -310,33 +76,12 @@ static bool is_local_file_modified(obs_properties_t *props,
return true;
}
static bool is_advanced_modified(obs_properties_t *props,
obs_property_t *prop, obs_data_t *settings)
{
UNUSED_PARAMETER(prop);
bool enabled = obs_data_get_bool(settings, "advanced");
obs_property_t *fscale = obs_properties_get(props, "force_scale");
obs_property_t *abuf = obs_properties_get(props, "audio_buffer_size");
obs_property_t *vbuf = obs_properties_get(props, "video_buffer_size");
obs_property_t *frame_drop = obs_properties_get(props, "frame_drop");
obs_property_t *color_range = obs_properties_get(props, "color_range");
obs_property_set_visible(fscale, enabled);
obs_property_set_visible(abuf, enabled);
obs_property_set_visible(vbuf, enabled);
obs_property_set_visible(frame_drop, enabled);
obs_property_set_visible(color_range, enabled);
return true;
}
static void ffmpeg_source_defaults(obs_data_t *settings)
{
obs_data_set_default_bool(settings, "is_local_file", true);
obs_data_set_default_bool(settings, "looping", false);
obs_data_set_default_bool(settings, "clear_on_media_end", true);
obs_data_set_default_bool(settings, "restart_on_activate", true);
obs_data_set_default_bool(settings, "force_scale", true);
#if defined(_WIN32)
obs_data_set_default_bool(settings, "hw_decode", true);
#endif
@ -392,7 +137,8 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
dstr_free(&filter);
dstr_free(&path);
obs_properties_add_bool(props, "looping", obs_module_text("Looping"));
prop = obs_properties_add_bool(props, "looping",
obs_module_text("Looping"));
obs_properties_add_bool(props, "restart_on_activate",
obs_module_text("RestartWhenActivated"));
@ -409,46 +155,11 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
obs_properties_add_bool(props, "clear_on_media_end",
obs_module_text("ClearOnMediaEnd"));
prop = obs_properties_add_bool(props, "advanced",
obs_module_text("Advanced"));
prop = obs_properties_add_bool(props, "close_when_inactive",
obs_module_text("CloseFileWhenInactive"));
obs_property_set_modified_callback(prop, is_advanced_modified);
obs_properties_add_bool(props, "force_scale",
obs_module_text("ForceFormat"));
prop = obs_properties_add_int(props, "audio_buffer_size",
obs_module_text("AudioBufferSize"), 1, 9999, 1);
obs_property_set_visible(prop, false);
prop = obs_properties_add_int(props, "video_buffer_size",
obs_module_text("VideoBufferSize"), 1, 9999, 1);
obs_property_set_visible(prop, false);
prop = obs_properties_add_list(props, "frame_drop",
obs_module_text("FrameDropping"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(prop, obs_module_text("DiscardNone"),
AVDISCARD_NONE);
obs_property_list_add_int(prop, obs_module_text("DiscardDefault"),
AVDISCARD_DEFAULT);
obs_property_list_add_int(prop, obs_module_text("DiscardNonRef"),
AVDISCARD_NONREF);
obs_property_list_add_int(prop, obs_module_text("DiscardBiDir"),
AVDISCARD_BIDIR);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 67, 100)
obs_property_list_add_int(prop, obs_module_text("DiscardNonIntra"),
AVDISCARD_NONINTRA);
#endif
obs_property_list_add_int(prop, obs_module_text("DiscardNonKey"),
AVDISCARD_NONKEY);
obs_property_list_add_int(prop, obs_module_text("DiscardAll"),
AVDISCARD_ALL);
obs_property_set_visible(prop, false);
obs_property_set_long_description(prop,
obs_module_text("CloseFileWhenInactive.ToolTip"));
prop = obs_properties_add_list(props, "color_range",
obs_module_text("ColorRange"), OBS_COMBO_TYPE_LIST,
@ -460,89 +171,88 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
obs_property_list_add_int(prop, obs_module_text("ColorRange.Full"),
VIDEO_RANGE_FULL);
obs_property_set_visible(prop, false);
return props;
}
static const char *frame_drop_to_str(enum AVDiscard discard)
{
#define DISCARD_CASE(x) case AVDISCARD_ ## x: return "AVDISCARD_" #x
switch (discard)
{
DISCARD_CASE(NONE);
DISCARD_CASE(DEFAULT);
DISCARD_CASE(NONREF);
DISCARD_CASE(BIDIR);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 67, 100)
DISCARD_CASE(NONINTRA);
#endif
DISCARD_CASE(NONKEY);
DISCARD_CASE(ALL);
default: return "(Unknown)";
};
#undef DISCARD_CASE
}
static void dump_source_info(struct ffmpeg_source *s, const char *input,
const char *input_format, bool is_advanced)
const char *input_format)
{
FF_BLOG(LOG_INFO,
"settings:\n"
"\tinput: %s\n"
"\tinput_format: %s\n"
"\tis_looping: %s\n"
"\tis_forcing_scale: %s\n"
"\tis_hw_decoding: %s\n"
"\tis_clear_on_media_end: %s\n"
"\trestart_on_activate: %s",
"\trestart_on_activate: %s\n"
"\tclose_when_inactive: %s",
input ? input : "(null)",
input_format ? input_format : "(null)",
s->is_looping ? "yes" : "no",
s->is_forcing_scale ? "yes" : "no",
s->is_hw_decoding ? "yes" : "no",
s->is_clear_on_media_end ? "yes" : "no",
s->restart_on_activate ? "yes" : "no");
s->restart_on_activate ? "yes" : "no",
s->close_when_inactive ? "yes" : "no");
}
if (!is_advanced)
return;
static void get_frame(void *opaque, struct obs_source_frame *f)
{
struct ffmpeg_source *s = opaque;
obs_source_output_video(s->source, f);
}
FF_BLOG(LOG_INFO,
"advanced settings:\n"
"\taudio_buffer_size: %d\n"
"\tvideo_buffer_size: %d\n"
"\tframe_drop: %s",
s->audio_buffer_size,
s->video_buffer_size,
frame_drop_to_str(s->frame_drop));
static void preload_frame(void *opaque, struct obs_source_frame *f)
{
struct ffmpeg_source *s = opaque;
obs_source_preload_video(s->source, f);
}
static void get_audio(void *opaque, struct obs_source_audio *a)
{
struct ffmpeg_source *s = opaque;
obs_source_output_audio(s->source, a);
}
static void media_stopped(void *opaque)
{
struct ffmpeg_source *s = opaque;
if (s->is_clear_on_media_end) {
obs_source_output_video(s->source, NULL);
if (s->close_when_inactive)
s->destroy_media = true;
}
}
static void ffmpeg_source_open(struct ffmpeg_source *s)
{
if (s->input && *s->input)
s->media_valid = mp_media_init(&s->media,
s->input, s->input_format,
s, get_frame, get_audio, media_stopped,
preload_frame, s->is_hw_decoding, s->range);
}
static void ffmpeg_source_tick(void *data, float seconds)
{
struct ffmpeg_source *s = data;
if (s->destroy_media) {
if (s->media_valid) {
mp_media_free(&s->media);
s->media_valid = false;
}
s->destroy_media = false;
}
}
static void ffmpeg_source_start(struct ffmpeg_source *s)
{
if (s->demuxer != NULL)
ff_demuxer_free(s->demuxer);
if (!s->media_valid)
ffmpeg_source_open(s);
s->demuxer = ff_demuxer_init();
s->demuxer->options.is_hw_decoding = s->is_hw_decoding;
s->demuxer->options.is_looping = s->is_looping;
ff_demuxer_set_callbacks(&s->demuxer->video_callbacks,
video_frame, NULL,
NULL, NULL, NULL, s);
ff_demuxer_set_callbacks(&s->demuxer->audio_callbacks,
audio_frame, NULL,
NULL, NULL, NULL, s);
if (s->is_advanced) {
s->demuxer->options.audio_frame_queue_size =
s->audio_buffer_size;
s->demuxer->options.video_frame_queue_size =
s->video_buffer_size;
s->demuxer->options.frame_drop = s->frame_drop;
if (s->media_valid) {
mp_media_play(&s->media, s->is_looping);
obs_source_show_preloaded_video(s->source);
}
ff_demuxer_open(s->demuxer, s->input, s->input_format);
}
static void ffmpeg_source_update(void *data, obs_data_t *settings)
@ -550,7 +260,6 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
struct ffmpeg_source *s = data;
bool is_local_file = obs_data_get_bool(settings, "is_local_file");
bool is_advanced = obs_data_get_bool(settings, "advanced");
char *input;
char *input_format;
@ -562,56 +271,37 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
input = (char *)obs_data_get_string(settings, "local_file");
input_format = NULL;
s->is_looping = obs_data_get_bool(settings, "looping");
obs_source_set_flags(s->source, OBS_SOURCE_FLAG_UNBUFFERED);
} else {
input = (char *)obs_data_get_string(settings, "input");
input_format = (char *)obs_data_get_string(settings,
"input_format");
s->is_looping = false;
obs_source_set_flags(s->source, 0);
}
s->input = input ? bstrdup(input) : NULL;
s->input_format = input_format ? bstrdup(input_format) : NULL;
s->is_advanced = is_advanced;
s->is_hw_decoding = obs_data_get_bool(settings, "hw_decode");
s->is_clear_on_media_end = obs_data_get_bool(settings,
"clear_on_media_end");
s->restart_on_activate = obs_data_get_bool(settings,
"restart_on_activate");
s->is_forcing_scale = true;
s->range = VIDEO_RANGE_DEFAULT;
s->close_when_inactive = obs_data_get_bool(settings,
"close_when_inactive");
s->range = (enum video_range_type)obs_data_get_int(settings,
"color_range");
if (is_advanced) {
s->audio_buffer_size = (int)obs_data_get_int(settings,
"audio_buffer_size");
s->video_buffer_size = (int)obs_data_get_int(settings,
"video_buffer_size");
s->frame_drop = (enum AVDiscard)obs_data_get_int(settings,
"frame_drop");
s->is_forcing_scale = obs_data_get_bool(settings,
"force_scale");
s->range = (enum video_range_type)obs_data_get_int(settings,
"color_range");
if (s->audio_buffer_size < 1) {
s->audio_buffer_size = 1;
FF_BLOG(LOG_WARNING, "invalid audio_buffer_size %d",
s->audio_buffer_size);
}
if (s->video_buffer_size < 1) {
s->video_buffer_size = 1;
FF_BLOG(LOG_WARNING, "invalid audio_buffer_size %d",
s->audio_buffer_size);
}
if (s->frame_drop < AVDISCARD_NONE ||
s->frame_drop > AVDISCARD_ALL) {
s->frame_drop = AVDISCARD_DEFAULT;
FF_BLOG(LOG_WARNING, "invalid frame_drop %d",
s->frame_drop);
}
if (s->media_valid) {
mp_media_free(&s->media);
s->media_valid = false;
}
dump_source_info(s, input, input_format, is_advanced);
ffmpeg_source_open(s);
dump_source_info(s, input, input_format);
if (!s->restart_on_activate || obs_source_active(s->source))
ffmpeg_source_start(s);
}
@ -637,8 +327,8 @@ static void ffmpeg_source_destroy(void *data)
{
struct ffmpeg_source *s = data;
if (s->demuxer)
ff_demuxer_free(s->demuxer);
if (s->media_valid)
mp_media_free(&s->media);
if (s->sws_ctx != NULL)
sws_freeContext(s->sws_ctx);
@ -661,9 +351,8 @@ static void ffmpeg_source_deactivate(void *data)
struct ffmpeg_source *s = data;
if (s->restart_on_activate) {
if (s->demuxer != NULL) {
ff_demuxer_free(s->demuxer);
s->demuxer = NULL;
if (s->media_valid) {
mp_media_stop(&s->media);
if (s->is_clear_on_media_end)
obs_source_output_video(s->source, NULL);
@ -683,5 +372,6 @@ struct obs_source_info ffmpeg_source = {
.get_properties = ffmpeg_source_getproperties,
.activate = ffmpeg_source_activate,
.deactivate = ffmpeg_source_deactivate,
.video_tick = ffmpeg_source_tick,
.update = ffmpeg_source_update
};