deps: Add media-playback static lib

Intended to replace libff as the media playback library.  Intended to
use less threads and be more extensible.  It was nearly impossible to
modify libff without bursting a vein.
master
jp9000 2017-03-19 15:31:12 -07:00
parent 0941a9c13d
commit 2329c71dac
7 changed files with 1288 additions and 0 deletions

1
deps/CMakeLists.txt vendored
View File

@ -6,6 +6,7 @@ endif()
add_subdirectory(glad)
add_subdirectory(ipc-util)
add_subdirectory(libff)
add_subdirectory(media-playback)
add_subdirectory(file-updater)
if(WIN32)

36
deps/media-playback/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,36 @@
project(media-playback)
find_package(FFmpeg REQUIRED
COMPONENTS avcodec avdevice avutil avformat)
include_directories(
${CMAKE_SOURCE_DIR}/libobs
${FFMPEG_INCLUDE_DIRS}
)
set(media-playback_HEADERS
media-playback/decode.h
media-playback/media.h
)
set(media-playback_SOURCES
media-playback/decode.c
media-playback/media.c
)
add_library(media-playback STATIC
${media-playback_HEADERS}
${media-playback_SOURCES}
)
target_include_directories(media-playback
PUBLIC .
)
if(NOT MSVC)
if(NOT MINGW)
target_compile_options(media-playback PRIVATE -fPIC)
endif()
endif()
target_link_libraries(media-playback
${FFMPEG_LIBRARIES}
)

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2017 Hugh Bailey <obs.jim@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
static enum AVPixelFormat closest_format(enum AVPixelFormat fmt)
{
switch (fmt) {
case AV_PIX_FMT_YUYV422:
return AV_PIX_FMT_YUYV422;
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUVJ422P:
case AV_PIX_FMT_UYVY422:
case AV_PIX_FMT_YUV422P16LE:
case AV_PIX_FMT_YUV422P16BE:
case AV_PIX_FMT_YUV422P10BE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV422P9BE:
case AV_PIX_FMT_YUV422P9LE:
case AV_PIX_FMT_YVYU422:
case AV_PIX_FMT_YUV422P12BE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV422P14BE:
case AV_PIX_FMT_YUV422P14LE:
return AV_PIX_FMT_UYVY422;
case AV_PIX_FMT_NV12:
case AV_PIX_FMT_NV21:
return AV_PIX_FMT_NV12;
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
case AV_PIX_FMT_YUV411P:
case AV_PIX_FMT_UYYVYY411:
case AV_PIX_FMT_YUV410P:
case AV_PIX_FMT_YUV420P16LE:
case AV_PIX_FMT_YUV420P16BE:
case AV_PIX_FMT_YUV420P9BE:
case AV_PIX_FMT_YUV420P9LE:
case AV_PIX_FMT_YUV420P10BE:
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV420P12BE:
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV420P14BE:
case AV_PIX_FMT_YUV420P14LE:
return AV_PIX_FMT_YUV420P;
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_BGRA:
case AV_PIX_FMT_BGR0:
return fmt;
default:
break;
}
return AV_PIX_FMT_BGRA;
}

View File

@ -0,0 +1,316 @@
/*
* Copyright (c) 2017 Hugh Bailey <obs.jim@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "decode.h"
#include "media.h"
static AVCodec *find_hardware_decoder(enum AVCodecID id)
{
AVHWAccel *hwa = av_hwaccel_next(NULL);
AVCodec *c = NULL;
while (hwa) {
if (hwa->id == id) {
if (hwa->pix_fmt == AV_PIX_FMT_VDA_VLD ||
hwa->pix_fmt == AV_PIX_FMT_DXVA2_VLD ||
hwa->pix_fmt == AV_PIX_FMT_VAAPI_VLD) {
c = avcodec_find_decoder_by_name(hwa->name);
if (c)
break;
}
}
hwa = av_hwaccel_next(hwa);
}
return c;
}
static int mp_open_codec(struct mp_decode *d)
{
AVCodecContext *c;
int ret;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
c = avcodec_alloc_context3(d->codec);
if (!c) {
blog(LOG_WARNING, "MP: Failed to allocate context");
return -1;
}
ret = avcodec_parameters_to_context(c, d->stream->codecpar);
if (ret < 0)
goto fail;
#else
c = d->stream->codec;
#endif
if (c->thread_count == 1 &&
c->codec_id != AV_CODEC_ID_PNG &&
c->codec_id != AV_CODEC_ID_TIFF &&
c->codec_id != AV_CODEC_ID_JPEG2000 &&
c->codec_id != AV_CODEC_ID_MPEG4 &&
c->codec_id != AV_CODEC_ID_WEBP)
c->thread_count = 0;
ret = avcodec_open2(c, d->codec, NULL);
if (ret < 0)
goto fail;
d->decoder = c;
return ret;
fail:
avcodec_close(c);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
av_free(d->decoder);
#endif
return ret;
}
bool mp_decode_init(mp_media_t *m, enum AVMediaType type, bool hw)
{
struct mp_decode *d = type == AVMEDIA_TYPE_VIDEO ? &m->v : &m->a;
enum AVCodecID id;
AVStream *stream;
int ret;
memset(d, 0, sizeof(*d));
d->m = m;
d->audio = type == AVMEDIA_TYPE_AUDIO;
ret = av_find_best_stream(m->fmt, type, -1, -1, NULL, 0);
if (ret < 0)
return false;
stream = d->stream = m->fmt->streams[ret];
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
id = stream->codecpar->codec_id;
#else
id = stream->codec->codec_id;
#endif
if (hw) {
d->codec = find_hardware_decoder(id);
if (d->codec) {
ret = mp_open_codec(d);
if (ret < 0)
d->codec = NULL;
}
}
if (!d->codec) {
if (id == AV_CODEC_ID_VP8)
d->codec = avcodec_find_decoder_by_name("libvpx");
else if (id == AV_CODEC_ID_VP9)
d->codec = avcodec_find_decoder_by_name("libvpx-vp9");
if (!d->codec)
d->codec = avcodec_find_decoder(id);
if (!d->codec) {
blog(LOG_WARNING, "MP: Failed to find %s codec",
av_get_media_type_string(type));
return false;
}
ret = mp_open_codec(d);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to open %s decoder: %s",
av_get_media_type_string(type),
av_err2str(ret));
return false;
}
}
d->frame = av_frame_alloc();
if (!d->frame) {
blog(LOG_WARNING, "MP: Failed to allocate %s frame",
av_get_media_type_string(type));
return false;
}
if (d->codec->capabilities & CODEC_CAP_TRUNCATED)
d->decoder->flags |= CODEC_FLAG_TRUNCATED;
return true;
}
void mp_decode_clear_packets(struct mp_decode *d)
{
if (d->packet_pending) {
av_packet_unref(&d->orig_pkt);
d->packet_pending = false;
}
while (d->packets.size) {
AVPacket pkt;
circlebuf_pop_front(&d->packets, &pkt, sizeof(pkt));
av_packet_unref(&pkt);
}
}
void mp_decode_free(struct mp_decode *d)
{
mp_decode_clear_packets(d);
circlebuf_free(&d->packets);
if (d->decoder) {
avcodec_close(d->decoder);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 40, 101)
av_free(d->decoder);
#endif
}
if (d->frame)
av_free(d->frame);
memset(d, 0, sizeof(*d));
}
void mp_decode_push_packet(struct mp_decode *decode, AVPacket *packet)
{
circlebuf_push_back(&decode->packets, packet, sizeof(*packet));
}
static inline int64_t get_estimated_duration(struct mp_decode *d,
int64_t last_pts)
{
if (last_pts)
return d->frame_pts - last_pts;
if (d->audio) {
return av_rescale_q(d->frame->nb_samples,
(AVRational){1, d->frame->sample_rate},
(AVRational){1, 1000000000});
} else {
if (d->last_duration)
return d->last_duration;
return av_rescale_q(d->decoder->time_base.num,
d->decoder->time_base,
(AVRational){1, 1000000000});
}
}
bool mp_decode_next(struct mp_decode *d)
{
bool eof = d->m->eof;
int got_frame;
int ret;
d->frame_ready = false;
if (!eof && !d->packets.size)
return true;
while (!d->frame_ready) {
if (!d->packet_pending) {
if (!d->packets.size) {
if (eof) {
d->pkt.data = NULL;
d->pkt.size = 0;
} else {
return true;
}
} else {
circlebuf_pop_front(&d->packets, &d->orig_pkt,
sizeof(d->orig_pkt));
d->pkt = d->orig_pkt;
d->packet_pending = true;
}
}
if (d->audio) {
ret = avcodec_decode_audio4(d->decoder,
d->frame, &got_frame, &d->pkt);
} else {
if (d->m->is_network && !d->got_first_keyframe) {
if (d->pkt.flags & AV_PKT_FLAG_KEY) {
d->got_first_keyframe = true;
} else {
av_packet_unref(&d->orig_pkt);
av_init_packet(&d->orig_pkt);
av_init_packet(&d->pkt);
d->packet_pending = false;
return true;
}
}
ret = avcodec_decode_video2(d->decoder,
d->frame, &got_frame, &d->pkt);
}
if (!got_frame && ret == 0) {
d->eof = true;
return true;
}
if (ret < 0) {
blog(LOG_DEBUG, "MP: decode failed: %s",
av_err2str(ret));
if (d->packet_pending) {
av_packet_unref(&d->orig_pkt);
av_init_packet(&d->orig_pkt);
av_init_packet(&d->pkt);
d->packet_pending = false;
}
return true;
}
d->frame_ready = !!got_frame;
if (d->packet_pending) {
if (d->pkt.size) {
d->pkt.data += ret;
d->pkt.size -= ret;
}
if (d->pkt.size == 0) {
av_packet_unref(&d->orig_pkt);
av_init_packet(&d->orig_pkt);
av_init_packet(&d->pkt);
d->packet_pending = false;
}
}
}
if (d->frame_ready) {
int64_t last_pts = d->frame_pts;
d->frame_pts = av_rescale_q(d->frame->best_effort_timestamp,
d->stream->time_base,
(AVRational){1, 1000000000});
int64_t duration = d->frame->pkt_duration;
if (!duration)
duration = get_estimated_duration(d, last_pts);
else
duration = av_rescale_q(duration,
d->stream->time_base,
(AVRational){1, 1000000000});
d->last_duration = duration;
d->next_pts = d->frame_pts + duration;
}
return true;
}
void mp_decode_flush(struct mp_decode *d)
{
avcodec_flush_buffers(d->decoder);
mp_decode_clear_packets(d);
d->eof = false;
d->frame_pts = 0;
d->frame_ready = false;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2017 Hugh Bailey <obs.jim@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <util/circlebuf.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4204)
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <util/threading.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
struct mp_media;
struct mp_decode {
struct mp_media *m;
AVStream *stream;
bool audio;
AVCodecContext *decoder;
AVCodec *codec;
int64_t last_duration;
int64_t frame_pts;
int64_t next_pts;
AVFrame *frame;
bool got_first_keyframe;
bool frame_ready;
bool eof;
AVPacket orig_pkt;
AVPacket pkt;
bool packet_pending;
struct circlebuf packets;
};
extern bool mp_decode_init(struct mp_media *media, enum AVMediaType type,
bool hw);
extern void mp_decode_free(struct mp_decode *decode);
extern void mp_decode_clear_packets(struct mp_decode *decode);
extern void mp_decode_push_packet(struct mp_decode *decode, AVPacket *pkt);
extern bool mp_decode_next(struct mp_decode *decode);
extern void mp_decode_flush(struct mp_decode *decode);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,679 @@
/*
* Copyright (c) 2017 Hugh Bailey <obs.jim@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <obs.h>
#include <util/platform.h>
#include <assert.h>
#include "media.h"
#include "closest-format.h"
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
static int64_t base_sys_ts = 0;
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 enum video_colorspace convert_color_space(enum AVColorSpace s)
{
return s == AVCOL_SPC_BT709 ? VIDEO_CS_709 : VIDEO_CS_DEFAULT;
}
static inline enum video_range_type convert_color_range(enum AVColorRange r)
{
return r == AVCOL_RANGE_JPEG ? VIDEO_RANGE_FULL : VIDEO_RANGE_DEFAULT;
}
static inline struct mp_decode *get_packet_decoder(mp_media_t *media,
AVPacket *pkt)
{
if (media->has_audio && pkt->stream_index == media->a.stream->index)
return &media->a;
if (media->has_video && pkt->stream_index == media->v.stream->index)
return &media->v;
return NULL;
}
static int mp_media_next_packet(mp_media_t *media)
{
AVPacket new_pkt;
AVPacket pkt;
av_init_packet(&pkt);
new_pkt = pkt;
int ret = av_read_frame(media->fmt, &pkt);
if (ret < 0) {
if (ret != AVERROR_EOF)
blog(LOG_WARNING, "MP: av_read_frame failed: %d", ret);
return ret;
}
struct mp_decode *d = get_packet_decoder(media, &pkt);
if (d && pkt.size) {
av_packet_ref(&new_pkt, &pkt);
mp_decode_push_packet(d, &new_pkt);
}
av_packet_unref(&pkt);
return ret;
}
static inline bool mp_media_ready_to_start(mp_media_t *m)
{
if (m->has_audio && !m->a.eof && !m->a.frame_ready)
return false;
if (m->has_video && !m->v.eof && !m->v.frame_ready)
return false;
return true;
}
static inline bool mp_decode_frame(struct mp_decode *d)
{
return d->frame_ready || mp_decode_next(d);
}
static inline int get_sws_colorspace(enum AVColorSpace cs)
{
switch (cs) {
case AVCOL_SPC_BT709:
return SWS_CS_ITU709;
case AVCOL_SPC_FCC:
return SWS_CS_FCC;
case AVCOL_SPC_SMPTE170M:
return SWS_CS_SMPTE170M;
case AVCOL_SPC_SMPTE240M:
return SWS_CS_SMPTE240M;
default:
break;
}
return SWS_CS_ITU601;
}
static inline int get_sws_range(enum AVColorRange r)
{
return r == AVCOL_RANGE_JPEG ? 1 : 0;
}
#define FIXED_1_0 (1<<16)
static bool mp_media_init_scaling(mp_media_t *m)
{
int space = get_sws_colorspace(m->v.decoder->colorspace);
int range = get_sws_range(m->v.decoder->color_range);
const int *coeff = sws_getCoefficients(space);
m->swscale = sws_getCachedContext(NULL,
m->v.decoder->width, m->v.decoder->height,
m->v.decoder->pix_fmt,
m->v.decoder->width, m->v.decoder->height,
m->scale_format,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (!m->swscale) {
blog(LOG_WARNING, "MP: Failed to initialize scaler");
return false;
}
sws_setColorspaceDetails(m->swscale, coeff, range, coeff, range, 0,
FIXED_1_0, FIXED_1_0);
int ret = av_image_alloc(m->scale_pic, m->scale_linesizes,
m->v.decoder->width, m->v.decoder->height,
m->scale_format, 1);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to create scale pic data");
return false;
}
return true;
}
static bool mp_media_prepare_frames(mp_media_t *m)
{
while (!mp_media_ready_to_start(m)) {
if (!m->eof) {
int ret = mp_media_next_packet(m);
if (ret == AVERROR_EOF)
m->eof = true;
else if (ret < 0)
return false;
}
if (m->has_video && !mp_decode_frame(&m->v))
return false;
if (m->has_audio && !mp_decode_frame(&m->a))
return false;
}
if (m->has_video && m->v.frame_ready && !m->swscale) {
m->scale_format = closest_format(m->v.frame->format);
if (m->scale_format != m->v.frame->format) {
if (!mp_media_init_scaling(m)) {
return false;
}
}
}
return true;
}
static inline int64_t mp_media_get_next_min_pts(mp_media_t *m)
{
int64_t min_next_ns = 0x7FFFFFFFFFFFFFFFLL;
if (m->has_video && m->v.frame_ready) {
if (m->v.frame_pts < min_next_ns)
min_next_ns = m->v.frame_pts;
}
if (m->has_audio && m->a.frame_ready) {
if (m->a.frame_pts < min_next_ns)
min_next_ns = m->a.frame_pts;
}
return min_next_ns;
}
static inline int64_t mp_media_get_base_pts(mp_media_t *m)
{
int64_t base_ts = 0;
if (m->has_video && m->v.next_pts > base_ts)
base_ts = m->v.next_pts;
if (m->has_audio && m->a.next_pts > base_ts)
base_ts = m->a.next_pts;
return base_ts;
}
static inline bool mp_media_can_play_frame(mp_media_t *m,
struct mp_decode *d)
{
return d->frame_ready && d->frame_pts <= m->next_pts_ns;
}
static void mp_media_next_audio(mp_media_t *m)
{
struct mp_decode *d = &m->a;
struct obs_source_audio audio = {0};
AVFrame *f = d->frame;
if (!mp_media_can_play_frame(m, d))
return;
d->frame_ready = false;
if (!m->a_cb)
return;
for (size_t i = 0; i < MAX_AV_PLANES; i++)
audio.data[i] = f->data[i];
audio.samples_per_sec = f->sample_rate;
audio.speakers = (enum speaker_layout)f->channels;
audio.format = convert_sample_format(f->format);
audio.frames = f->nb_samples;
audio.timestamp = m->base_ts + d->frame_pts - m->start_ts +
m->play_sys_ts - base_sys_ts;
if (audio.format == AUDIO_FORMAT_UNKNOWN)
return;
m->a_cb(m->opaque, &audio);
}
static void mp_media_next_video(mp_media_t *m, bool preload)
{
struct mp_decode *d = &m->v;
struct obs_source_frame *frame = &m->obsframe;
enum video_format new_format;
enum video_colorspace new_space;
enum video_range_type new_range;
AVFrame *f = d->frame;
if (!preload) {
if (!mp_media_can_play_frame(m, d))
return;
d->frame_ready = false;
if (!m->v_cb)
return;
} else if (!d->frame_ready) {
return;
}
if (m->swscale) {
int ret = sws_scale(m->swscale,
(const uint8_t *const *)f->data, f->linesize,
0, f->height,
m->scale_pic, m->scale_linesizes);
if (ret < 0)
return;
for (size_t i = 0; i < 4; i++) {
frame->data[i] = m->scale_pic[i];
frame->linesize[i] = m->scale_linesizes[i];
}
} else {
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
frame->data[i] = f->data[i];
frame->linesize[i] = f->linesize[i];
}
}
new_format = convert_pixel_format(m->scale_format);
new_space = convert_color_space(f->colorspace);
new_range = m->force_range == VIDEO_RANGE_DEFAULT
? convert_color_range(f->color_range)
: m->force_range;
if (new_format != frame->format ||
new_space != m->cur_space ||
new_range != m->cur_range) {
bool success;
frame->format = new_format;
frame->full_range = new_range == VIDEO_RANGE_FULL;
success = video_format_get_parameters(
new_space,
new_range,
frame->color_matrix,
frame->color_range_min,
frame->color_range_max);
frame->format = new_format;
m->cur_space = new_space;
m->cur_range = new_range;
if (!success) {
frame->format = VIDEO_FORMAT_NONE;
return;
}
}
if (frame->format == VIDEO_FORMAT_NONE)
return;
frame->timestamp = m->base_ts + d->frame_pts - m->start_ts +
m->play_sys_ts - base_sys_ts;
frame->width = f->width;
frame->height = f->height;
frame->flip = false;
if (preload)
m->v_preload_cb(m->opaque, frame);
else
m->v_cb(m->opaque, frame);
}
static void mp_media_calc_next_ns(mp_media_t *m)
{
int64_t min_next_ns = mp_media_get_next_min_pts(m);
int64_t delta = min_next_ns - m->next_pts_ns;
#ifdef _DEBUG
assert(delta >= 0);
#endif
if (delta < 0)
delta = 0;
if (delta > 3000000000)
delta = 0;
m->next_ns += delta;
m->next_pts_ns = min_next_ns;
}
static bool mp_media_reset(mp_media_t *m)
{
AVStream *stream = m->fmt->streams[0];
int64_t seek_pos;
int seek_flags;
bool stopping;
bool active;
if (m->fmt->duration == AV_NOPTS_VALUE) {
seek_pos = 0;
seek_flags = AVSEEK_FLAG_FRAME;
} else {
seek_pos = m->fmt->start_time;
seek_flags = AVSEEK_FLAG_BACKWARD;
}
int64_t seek_target = seek_flags == AVSEEK_FLAG_BACKWARD
? av_rescale_q(seek_pos, AV_TIME_BASE_Q, stream->time_base)
: seek_pos;
int ret = av_seek_frame(m->fmt, 0, seek_target, seek_flags);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to seek: %s", av_err2str(ret));
return false;
}
if (m->has_video && !m->is_network)
mp_decode_flush(&m->v);
if (m->has_audio && !m->is_network)
mp_decode_flush(&m->a);
int64_t next_ts = mp_media_get_base_pts(m);
int64_t offset = next_ts - m->next_pts_ns;
m->eof = false;
m->base_ts += next_ts;
if (!mp_media_prepare_frames(m))
return false;
pthread_mutex_lock(&m->mutex);
stopping = m->stopping;
active = m->active;
m->stopping = false;
pthread_mutex_unlock(&m->mutex);
if (active) {
if (!m->play_sys_ts)
m->play_sys_ts = (int64_t)os_gettime_ns();
m->start_ts = m->next_pts_ns = mp_media_get_next_min_pts(m);
if (m->next_ns)
m->next_ns += offset;
} else {
m->start_ts = m->next_pts_ns = mp_media_get_next_min_pts(m);
m->play_sys_ts = (int64_t)os_gettime_ns();
m->next_ns = 0;
}
if (!active && !m->is_network && m->v_preload_cb)
mp_media_next_video(m, true);
if (stopping && m->stop_cb)
m->stop_cb(m->opaque);
return true;
}
static inline void mp_media_sleepto(mp_media_t *m)
{
if (!m->next_ns)
m->next_ns = os_gettime_ns();
else
os_sleepto_ns(m->next_ns);
}
static inline bool mp_media_eof(mp_media_t *m)
{
bool v_ended = !m->has_video || !m->v.frame_ready;
bool a_ended = !m->has_audio || !m->a.frame_ready;
bool eof = v_ended && a_ended;
if (eof) {
bool looping;
pthread_mutex_lock(&m->mutex);
looping = m->looping;
if (!looping) {
m->active = false;
m->stopping = true;
}
pthread_mutex_unlock(&m->mutex);
mp_media_reset(m);
}
return eof;
}
static void *mp_media_thread(void *opaque)
{
mp_media_t *m = opaque;
os_set_thread_name("mp_media_thread");
mp_media_reset(m);
for (;;) {
bool reset, kill, is_active;
pthread_mutex_lock(&m->mutex);
is_active = m->active;
pthread_mutex_unlock(&m->mutex);
if (!is_active) {
if (os_sem_wait(m->sem) < 0)
return NULL;
} else {
mp_media_sleepto(m);
}
pthread_mutex_lock(&m->mutex);
reset = m->reset;
kill = m->kill;
m->reset = false;
m->kill = false;
pthread_mutex_unlock(&m->mutex);
if (kill) {
break;
}
if (reset) {
mp_media_reset(m);
continue;
}
/* frames are ready */
if (is_active) {
if (m->has_video)
mp_media_next_video(m, false);
if (m->has_audio)
mp_media_next_audio(m);
if (!mp_media_prepare_frames(m))
return NULL;
if (mp_media_eof(m))
continue;
mp_media_calc_next_ns(m);
}
}
return NULL;
}
static inline bool mp_media_init_internal(mp_media_t *m,
const char *path,
const char *format_name,
bool hw)
{
AVInputFormat *format = NULL;
if (pthread_mutex_init(&m->mutex, NULL) != 0) {
blog(LOG_WARNING, "MP: Failed to init mutex");
return false;
}
if (os_sem_init(&m->sem, 0) != 0) {
blog(LOG_WARNING, "MP: Failed to init semaphore");
return false;
}
if (format_name && *format_name) {
format = av_find_input_format(format_name);
if (!format)
blog(LOG_INFO, "MP: Unable to find input format for "
"'%s'", path);
}
int ret = avformat_open_input(&m->fmt, path, format, NULL);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to open media: '%s'", path);
return false;
}
if (avformat_find_stream_info(m->fmt, NULL) < 0) {
blog(LOG_WARNING, "MP: Failed to find stream info for '%s'",
path);
return false;
}
m->has_video = mp_decode_init(m, AVMEDIA_TYPE_VIDEO, hw);
m->has_audio = mp_decode_init(m, AVMEDIA_TYPE_AUDIO, hw);
if (!m->has_video && !m->has_audio) {
blog(LOG_WARNING, "MP: Could not initialize audio or video: "
"'%s'", path);
return false;
}
if (pthread_create(&m->thread, NULL, mp_media_thread, m) != 0) {
blog(LOG_WARNING, "MP: Could not create media thread");
return false;
}
m->thread_valid = true;
return true;
}
bool mp_media_init(mp_media_t *media,
const char *path,
const char *format,
void *opaque,
mp_video_cb v_cb,
mp_audio_cb a_cb,
mp_stop_cb stop_cb,
mp_video_cb v_preload_cb,
bool hw_decoding,
enum video_range_type force_range)
{
memset(media, 0, sizeof(*media));
pthread_mutex_init_value(&media->mutex);
media->opaque = opaque;
media->v_cb = v_cb;
media->a_cb = a_cb;
media->stop_cb = stop_cb;
media->v_preload_cb = v_preload_cb;
media->force_range = force_range;
if (path && *path)
media->is_network = !!strstr(path, "://");
static bool initialized = false;
if (!initialized) {
av_register_all();
avdevice_register_all();
avcodec_register_all();
avformat_network_init();
initialized = true;
}
if (!base_sys_ts)
base_sys_ts = (int64_t)os_gettime_ns();
if (!mp_media_init_internal(media, path, format, hw_decoding)) {
mp_media_free(media);
return false;
}
return true;
}
static void mp_kill_thread(mp_media_t *m)
{
if (m->thread_valid) {
pthread_mutex_lock(&m->mutex);
m->kill = true;
pthread_mutex_unlock(&m->mutex);
os_sem_post(m->sem);
pthread_join(m->thread, NULL);
}
}
void mp_media_free(mp_media_t *media)
{
if (!media)
return;
mp_media_stop(media);
mp_kill_thread(media);
mp_decode_free(&media->v);
mp_decode_free(&media->a);
pthread_mutex_destroy(&media->mutex);
os_sem_destroy(media->sem);
avformat_close_input(&media->fmt);
sws_freeContext(media->swscale);
av_freep(&media->scale_pic[0]);
memset(media, 0, sizeof(*media));
pthread_mutex_init_value(&media->mutex);
}
void mp_media_play(mp_media_t *m, bool loop)
{
pthread_mutex_lock(&m->mutex);
if (m->active)
m->reset = true;
m->looping = loop;
m->active = true;
pthread_mutex_unlock(&m->mutex);
os_sem_post(m->sem);
}
void mp_media_stop(mp_media_t *m)
{
pthread_mutex_lock(&m->mutex);
if (m->active) {
m->reset = true;
m->active = false;
m->stopping = true;
os_sem_post(m->sem);
}
pthread_mutex_unlock(&m->mutex);
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2017 Hugh Bailey <obs.jim@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <obs.h>
#include "decode.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4204)
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <util/threading.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
typedef void (*mp_video_cb)(void *opaque, struct obs_source_frame *frame);
typedef void (*mp_audio_cb)(void *opaque, struct obs_source_audio *audio);
typedef void (*mp_stop_cb)(void *opaque);
struct mp_media {
AVFormatContext *fmt;
mp_video_cb v_preload_cb;
mp_stop_cb stop_cb;
mp_video_cb v_cb;
mp_audio_cb a_cb;
void *opaque;
enum AVPixelFormat scale_format;
struct SwsContext *swscale;
int scale_linesizes[4];
uint8_t *scale_pic[4];
struct mp_decode v;
struct mp_decode a;
bool is_network;
bool has_video;
bool has_audio;
bool is_file;
bool eof;
struct obs_source_frame obsframe;
enum video_colorspace cur_space;
enum video_range_type cur_range;
enum video_range_type force_range;
int64_t play_sys_ts;
int64_t next_pts_ns;
uint64_t next_ns;
int64_t start_ts;
int64_t base_ts;
pthread_mutex_t mutex;
os_sem_t *sem;
bool stopping;
bool looping;
bool active;
bool reset;
bool kill;
bool thread_valid;
pthread_t thread;
};
typedef struct mp_media mp_media_t;
extern bool mp_media_init(mp_media_t *media,
const char *path,
const char *format,
void *opaque,
mp_video_cb v_cb,
mp_audio_cb a_cb,
mp_stop_cb stop_cb,
mp_video_cb v_preload_cb,
bool hardware_decoding,
enum video_range_type force_range);
extern void mp_media_free(mp_media_t *media);
extern void mp_media_play(mp_media_t *media, bool loop);
extern void mp_media_stop(mp_media_t *media);
#ifdef __cplusplus
}
#endif