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
parent
0941a9c13d
commit
2329c71dac
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
)
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue