/*
* This file is part of bino, a 3D video player.
*
* Copyright (C) 2010, 2011, 2012
* Martin Lambers
* Frédéric Devernay
* Joe
* D. Matz
*
* 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 3 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 .
*/
/* Modified August 2012 by Gary Bilkus to include initial limited support for filtering */
#include "config.h"
extern "C"
{
#define __STDC_CONSTANT_MACROS
#include
#include
#include
#include
#include
#include
#include
}
#include
#include
#include
#include
#include
#if HAVE_SYSCONF
# include
#else
# include
#endif
#include "gettext.h"
#define _(string) gettext(string)
#include "dbg.h"
#include "blob.h"
#include "exc.h"
#include "msg.h"
#include "str.h"
#include "thread.h"
#include "media_object.h"
// filtering test. This should be replaced with a proper parameter or option.....
char *blixfilter = "yadif=1";
// The read thread.
// This thread reads packets from the AVFormatContext and stores them in the
// appropriate packet queues.
class read_thread : public thread
{
private:
const std::string _url;
const bool _is_device;
struct ffmpeg_stuff *_ffmpeg;
bool _eof;
public:
read_thread(const std::string &url, bool is_device, struct ffmpeg_stuff *ffmpeg);
void run();
void reset();
bool eof() const
{
return _eof;
}
};
// The video decode thread.
// This thread reads packets from its packet queue and decodes them to video frames.
class video_decode_thread : public thread
{
private:
std::string _url;
struct ffmpeg_stuff *_ffmpeg;
int _video_stream;
int _raw_frames;
bool _eof;
int64_t handle_timestamp(int64_t timestamp);
public:
video_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int video_stream);
void set_raw_frames(int raw_frames)
{
_raw_frames = raw_frames;
}
bool eof() const
{
return _eof;
}
void run();
};
class video_filter_thread : public thread
{
private:
struct ffmpeg_stuff *_ffmpeg;
int _filter_stream;
video_frame _frame;
int _video_stream;
int _raw_frames;
int64_t handle_timestamp(int64_t timestamp);
public:
video_filter_thread( struct ffmpeg_stuff *ffmpeg, int video_stream);
void set_raw_frames(int raw_frames)
{
_raw_frames = raw_frames;
}
void run();
const video_frame &frame()
{
return _frame;
}
};
// The audio decode thread.
// This thread reads packets from its packet queue and decodes them to audio blobs.
class audio_decode_thread : public thread
{
private:
std::string _url;
struct ffmpeg_stuff *_ffmpeg;
int _audio_stream;
audio_blob _blob;
int64_t handle_timestamp(int64_t timestamp);
public:
audio_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int audio_stream);
void run();
const audio_blob &blob()
{
return _blob;
}
};
// The subtitle decode thread.
// This thread reads packets from its packet queue and decodes them to subtitle boxes.
class subtitle_decode_thread : public thread
{
private:
std::string _url;
struct ffmpeg_stuff *_ffmpeg;
int _subtitle_stream;
subtitle_box _box;
int64_t handle_timestamp(int64_t timestamp);
public:
subtitle_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int subtitle_stream);
void run();
const subtitle_box &box()
{
return _box;
}
};
// Hide the FFmpeg stuff so that their messy header files cannot cause problems
// in other source files.
static const size_t audio_tmpbuf_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2;
struct ffmpeg_stuff
{
AVFormatContext *format_ctx;
bool have_active_audio_stream;
int64_t pos;
read_thread *reader;
std::vector video_streams;
std::vector video_codec_ctxs;
std::vector video_frame_templates;
std::vector video_sws_ctxs;
std::vector video_codecs;
std::vector > video_packet_queues;
std::vector video_packet_queue_mutexes;
std::vector video_packets;
std::vector video_decode_threads;
std::vector video_filter_threads;
std::vector video_frames;
std::vector video_buffered_frames;
std::vector video_buffers;
std::vector video_sws_frames;
std::vector video_sws_buffers;
std::vector video_last_timestamps;
std::vector > video_frame_queues;
std::vector > video_frame_presentation_times;
std::vector video_frame_queue_mutexes;
std::vector audio_streams;
std::vector audio_codec_ctxs;
std::vector audio_blob_templates;
std::vector audio_codecs;
std::vector > audio_packet_queues;
std::vector audio_packet_queue_mutexes;
std::vector audio_decode_threads;
std::vector audio_tmpbufs;
std::vector audio_blobs;
std::vector > audio_buffers;
std::vector audio_last_timestamps;
std::vector subtitle_streams;
std::vector subtitle_codec_ctxs;
std::vector subtitle_box_templates;
std::vector subtitle_codecs;
std::vector > subtitle_packet_queues;
std::vector subtitle_packet_queue_mutexes;
std::vector subtitle_decode_threads;
std::vector > subtitle_box_buffers;
std::vector subtitle_last_timestamps;
bool filtering;
std::vector buffersink_ctxs;
std::vector buffersrc_ctxs;
};
// Use one decoding thread per processor for video decoding.
static int video_decoding_threads()
{
static long n = -1;
if (n < 0)
{
#ifdef HAVE_SYSCONF
n = sysconf(_SC_NPROCESSORS_ONLN);
#else
SYSTEM_INFO si;
GetSystemInfo(&si);
n = si.dwNumberOfProcessors;
#endif
if (n < 1)
{
n = 1;
}
else if (n > 16)
{
n = 16;
}
}
return n;
}
// Return FFmpeg error as std::string.
static std::string my_av_strerror(int err)
{
blob b(1024);
av_strerror(err, b.ptr(), b.size());
return std::string(b.ptr());
}
// Convert FFmpeg log messages to our log messages.
static void my_av_log(void *ptr, int level, const char *fmt, va_list vl)
{
static mutex line_mutex;
static std::string line;
if (level > av_log_get_level())
{
return;
}
line_mutex.lock();
std::string p;
AVClass* avc = ptr ? *reinterpret_cast(ptr) : NULL;
if (avc)
{
p = str::asprintf("[%s @ %p] ", avc->item_name(ptr), ptr);
}
std::string s = str::vasprintf(fmt, vl);
bool line_ends = false;
if (s.length() > 0)
{
if (s[s.length() - 1] == '\n')
{
line_ends = true;
s.erase(s.length() - 1);
}
}
line += s;
if (line_ends)
{
msg::level_t l;
switch (level)
{
case AV_LOG_PANIC:
case AV_LOG_FATAL:
case AV_LOG_ERROR:
l = msg::ERR;
break;
case AV_LOG_WARNING:
l = msg::WRN;
break;
case AV_LOG_INFO:
case AV_LOG_VERBOSE:
case AV_LOG_DEBUG:
default:
l = msg::DBG;
break;
}
size_t n;
while ((n = line.find('\n')) != std::string::npos)
{
msg::msg(l, std::string("FFmpeg: ") + p + line.substr(0, n));
line = line.substr(n + 1);
}
msg::msg(l, std::string("FFmpeg: ") + p + line);
line.clear();
}
line_mutex.unlock();
}
// Handle timestamps
static int64_t timestamp_helper(int64_t &last_timestamp, int64_t timestamp)
{
if (timestamp == std::numeric_limits::min())
{
timestamp = last_timestamp;
}
last_timestamp = timestamp;
return timestamp;
}
// Get a stream duration
static int64_t stream_duration(AVStream *stream, AVFormatContext *format)
{
// Try to get duration from the stream first. If that fails, fall back to
// the value provided by the container.
int64_t duration = stream->duration;
if (duration > 0)
{
AVRational time_base = stream->time_base;
return duration * 1000000 * time_base.num / time_base.den;
}
else
{
duration = format->duration;
return duration * 1000000 / AV_TIME_BASE;
}
}
// Get a file name (or URL) extension, if any
static std::string get_extension(const std::string& url)
{
std::string extension;
size_t dot_pos = url.find_last_of('.');
if (dot_pos != std::string::npos) {
extension = url.substr(dot_pos + 1);
for (size_t i = 0; i < extension.length(); i++)
extension[i] = std::tolower(extension[i]);
}
return extension;
}
media_object::media_object(bool always_convert_to_bgra32) :
_always_convert_to_bgra32(always_convert_to_bgra32), _ffmpeg(NULL)
{
avdevice_register_all();
av_register_all();
#if LIBAVFORMAT_VERSION_MAJOR >= 53 && LIBAVFORMAT_VERSION_MINOR >= 13
avformat_network_init();
#endif
switch (msg::level())
{
case msg::DBG:
av_log_set_level(AV_LOG_DEBUG);
break;
case msg::INF:
av_log_set_level(AV_LOG_INFO);
break;
case msg::WRN:
av_log_set_level(AV_LOG_WARNING);
break;
case msg::ERR:
av_log_set_level(AV_LOG_ERROR);
break;
case msg::REQ:
default:
av_log_set_level(AV_LOG_FATAL);
break;
}
av_log_set_callback(my_av_log);
msg::wrn(_("Filter setup v7"));
avcodec_register_all();
avfilter_register_all();
msg::wrn(_("Filter setup completed"));
}
media_object::~media_object()
{
if (_ffmpeg)
{
close();
}
}
void media_object::set_video_frame_template(int index, int width_before_avcodec_open, int height_before_avcodec_open)
{
AVStream *video_stream = _ffmpeg->format_ctx->streams[_ffmpeg->video_streams[index]];
AVCodecContext *video_codec_ctx = _ffmpeg->video_codec_ctxs[index];
int vcheight = video_codec_ctx->height;
int vcwidth = video_codec_ctx->width;
int pix_fmt = video_codec_ctx->pix_fmt;
int sarnum = video_codec_ctx->sample_aspect_ratio.num;
int sarden = video_codec_ctx->sample_aspect_ratio.den;
int colorspace = video_codec_ctx->colorspace;
int color_range = video_codec_ctx->color_range;
int chroma_sample_location = video_codec_ctx->chroma_sample_location;
if (_ffmpeg->filtering)
{
}
video_frame &video_frame_template = _ffmpeg->video_frame_templates[index];
// Dimensions and aspect ratio
video_frame_template.raw_width = vcwidth;
video_frame_template.raw_height = vcheight;
// XXX Use width/height values from before avcodec_open() if they
// differ and seem safe to use. See also media_object::open().
if (width_before_avcodec_open >= 1
&& height_before_avcodec_open >= 1
&& width_before_avcodec_open <= vcwidth
&& height_before_avcodec_open <= vcheight
&& (width_before_avcodec_open != vcwidth
|| height_before_avcodec_open != vcheight))
{
msg::dbg("%s video stream %d: using frame size %dx%d instead of %dx%d.",
_url.c_str(), index + 1,
width_before_avcodec_open, height_before_avcodec_open,
vcwidth, vcheight);
video_frame_template.raw_width = width_before_avcodec_open;
video_frame_template.raw_height = height_before_avcodec_open;
}
int ar_num = 1;
int ar_den = 1;
int ar_snum = video_stream->sample_aspect_ratio.num;
int ar_sden = video_stream->sample_aspect_ratio.den;
int ar_cnum = sarnum;
int ar_cden = sarden;
if (ar_cnum > 0 && ar_cden > 0)
{
ar_num = ar_cnum;
ar_den = ar_cden;
}
else if (ar_snum > 0 && ar_sden > 0)
{
ar_num = ar_snum;
ar_den = ar_sden;
}
video_frame_template.raw_aspect_ratio =
static_cast(ar_num * video_frame_template.raw_width)
/ static_cast(ar_den * video_frame_template.raw_height);
// Data layout and color space
video_frame_template.layout = video_frame::bgra32;
video_frame_template.color_space = video_frame::srgb;
video_frame_template.value_range = video_frame::u8_full;
video_frame_template.chroma_location = video_frame::center;
if (!_always_convert_to_bgra32
&& (pix_fmt == PIX_FMT_YUV444P
|| pix_fmt == PIX_FMT_YUV444P10
|| pix_fmt == PIX_FMT_YUV422P
|| pix_fmt == PIX_FMT_YUV422P10
|| pix_fmt == PIX_FMT_YUV420P
|| pix_fmt == PIX_FMT_YUV420P10))
{
if (pix_fmt == PIX_FMT_YUV444P
|| pix_fmt == PIX_FMT_YUV444P10)
{
video_frame_template.layout = video_frame::yuv444p;
}
else if (pix_fmt == PIX_FMT_YUV422P
|| pix_fmt == PIX_FMT_YUV422P10)
{
video_frame_template.layout = video_frame::yuv422p;
}
else
{
video_frame_template.layout = video_frame::yuv420p;
}
video_frame_template.color_space = video_frame::yuv601;
if (colorspace == AVCOL_SPC_BT709)
{
video_frame_template.color_space = video_frame::yuv709;
}
if (pix_fmt == PIX_FMT_YUV444P10
|| pix_fmt == PIX_FMT_YUV422P10
|| pix_fmt == PIX_FMT_YUV420P10)
{
video_frame_template.value_range = video_frame::u10_mpeg;
if (color_range == AVCOL_RANGE_JPEG)
{
video_frame_template.value_range = video_frame::u10_full;
}
}
else
{
video_frame_template.value_range = video_frame::u8_mpeg;
if (color_range == AVCOL_RANGE_JPEG)
{
video_frame_template.value_range = video_frame::u8_full;
}
}
video_frame_template.chroma_location = video_frame::center;
if (chroma_sample_location == AVCHROMA_LOC_LEFT)
{
video_frame_template.chroma_location = video_frame::left;
}
else if (chroma_sample_location == AVCHROMA_LOC_TOPLEFT)
{
video_frame_template.chroma_location = video_frame::topleft;
}
}
else if (!_always_convert_to_bgra32
&& (pix_fmt == PIX_FMT_YUVJ444P
|| pix_fmt == PIX_FMT_YUVJ422P
|| pix_fmt == PIX_FMT_YUVJ420P))
{
if (pix_fmt == PIX_FMT_YUVJ444P)
{
video_frame_template.layout = video_frame::yuv444p;
}
else if (pix_fmt == PIX_FMT_YUVJ422P)
{
video_frame_template.layout = video_frame::yuv422p;
}
else
{
video_frame_template.layout = video_frame::yuv420p;
}
video_frame_template.color_space = video_frame::yuv601;
video_frame_template.value_range = video_frame::u8_full;
video_frame_template.chroma_location = video_frame::center;
}
// Stereo layout
video_frame_template.stereo_layout = parameters::layout_mono;
video_frame_template.stereo_layout_swap = false;
std::string val;
/* Determine the stereo layout from the resolution.*/
if (video_frame_template.raw_width / 2 > video_frame_template.raw_height)
{
video_frame_template.stereo_layout = parameters::layout_left_right;
}
else if (video_frame_template.raw_height > video_frame_template.raw_width)
{
video_frame_template.stereo_layout = parameters::layout_top_bottom;
}
/* Gather hints from the filename extension */
std::string extension = get_extension(_url);
if (extension == "mpo")
{
/* MPO files are alternating-left-right. */
video_frame_template.stereo_layout = parameters::layout_alternating;
}
else if (extension == "jps" || extension == "pns")
{
/* JPS and PNS are side-by-side in right-left mode */
video_frame_template.stereo_layout = parameters::layout_left_right;
video_frame_template.stereo_layout_swap = true;
}
/* Determine the input mode by looking at the file name.
* This should be compatible to these conventions:
* http://www.tru3d.com/technology/3D_Media_Formats_Software.php?file=TriDef%20Supported%203D%20Formats */
std::string marker = _url.substr(0, _url.find_last_of('.'));
size_t last_dash = marker.find_last_of('-');
if (last_dash != std::string::npos)
{
marker = marker.substr(last_dash + 1);
}
else
{
marker = "";
}
for (size_t i = 0; i < marker.length(); i++)
{
marker[i] = std::tolower(marker[i]);
}
if (marker == "lr")
{
video_frame_template.stereo_layout = parameters::layout_left_right;
video_frame_template.stereo_layout_swap = false;
}
else if (marker == "rl")
{
video_frame_template.stereo_layout = parameters::layout_left_right;
video_frame_template.stereo_layout_swap = true;
}
else if (marker == "lrh" || marker == "lrq")
{
video_frame_template.stereo_layout = parameters::layout_left_right_half;
video_frame_template.stereo_layout_swap = false;
}
else if (marker == "rlh" || marker == "rlq")
{
video_frame_template.stereo_layout = parameters::layout_left_right_half;
video_frame_template.stereo_layout_swap = true;
}
else if (marker == "tb" || marker == "ab")
{
video_frame_template.stereo_layout = parameters::layout_top_bottom;
video_frame_template.stereo_layout_swap = false;
}
else if (marker == "bt" || marker == "ba")
{
video_frame_template.stereo_layout = parameters::layout_top_bottom;
video_frame_template.stereo_layout_swap = true;
}
else if (marker == "tbh" || marker == "abq")
{
video_frame_template.stereo_layout = parameters::layout_top_bottom_half;
video_frame_template.stereo_layout_swap = false;
}
else if (marker == "bth" || marker == "baq")
{
video_frame_template.stereo_layout = parameters::layout_top_bottom_half;
video_frame_template.stereo_layout_swap = true;
}
else if (marker == "eo")
{
video_frame_template.stereo_layout = parameters::layout_even_odd_rows;
video_frame_template.stereo_layout_swap = false;
// all image lines are given in this case, and there should be no interpolation [TODO]
}
else if (marker == "oe")
{
video_frame_template.stereo_layout = parameters::layout_even_odd_rows;
video_frame_template.stereo_layout_swap = true;
// all image lines are given in this case, and there should be no interpolation [TODO]
}
else if (marker == "eoq" || marker == "3dir")
{
video_frame_template.stereo_layout = parameters::layout_even_odd_rows;
video_frame_template.stereo_layout_swap = false;
}
else if (marker == "oeq" || marker == "3di")
{
video_frame_template.stereo_layout = parameters::layout_even_odd_rows;
video_frame_template.stereo_layout_swap = true;
}
else if (marker == "2d")
{
video_frame_template.stereo_layout = parameters::layout_mono;
video_frame_template.stereo_layout_swap = false;
}
/* Check some tags defined at this link: http://www.3dtv.at/Knowhow/StereoWmvSpec_en.aspx
* This is necessary to make the example movies provided by 3dtv.at work out of the box. */
val = tag_value("StereoscopicLayout");
if (val == "SideBySideRF" || val == "SideBySideLF")
{
video_frame_template.stereo_layout_swap = (val == "SideBySideRF");
val = tag_value("StereoscopicHalfWidth");
video_frame_template.stereo_layout = (val == "1" ? parameters::layout_left_right_half : parameters::layout_left_right);
}
else if (val == "OverUnderRT" || val == "OverUnderLT")
{
video_frame_template.stereo_layout_swap = (val == "OverUnderRT");
val = tag_value("StereoscopicHalfHeight");
video_frame_template.stereo_layout = (val == "1" ? parameters::layout_top_bottom_half : parameters::layout_top_bottom);
}
/* Check the Matroska StereoMode metadata, which is translated by FFmpeg to a "stereo_mode" tag.
* This tag is per-track, not per-file!
* This tag is the most reliable source of information about the stereo layout and should be used
* by everyone. Unfortunately, we still have to look at the resolution to guess whether we have
* a reduced resolution (*_half) stereo layout. */
val = "";
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(video_stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
{
if (std::string(tag->key) == "stereo_mode")
{
val = tag->value;
break;
}
}
if (val == "mono")
{
video_frame_template.stereo_layout = parameters::layout_mono;
video_frame_template.stereo_layout_swap = false;
}
else if (val == "left_right" || val == "right_left")
{
if (video_frame_template.raw_width / 2 > video_frame_template.raw_height)
{
video_frame_template.stereo_layout = parameters::layout_left_right;
}
else
{
video_frame_template.stereo_layout = parameters::layout_left_right_half;
}
video_frame_template.stereo_layout_swap = (val == "right_left");
}
else if (val == "top_bottom" || val == "bottom_top")
{
if (video_frame_template.raw_height > video_frame_template.raw_width)
{
video_frame_template.stereo_layout = parameters::layout_top_bottom;
}
else
{
video_frame_template.stereo_layout = parameters::layout_top_bottom_half;
}
video_frame_template.stereo_layout_swap = (val == "bottom_top");
}
else if (val == "row_interleaved_lr" || val == "row_interleaved_rl")
{
video_frame_template.stereo_layout = parameters::layout_even_odd_rows;
video_frame_template.stereo_layout_swap = (val == "row_interleaved_rl");
}
else if (val == "block_lr" || val == "block_rl")
{
video_frame_template.stereo_layout = parameters::layout_alternating;
video_frame_template.stereo_layout_swap = (val == "block_rl");
}
else if (!val.empty())
{
msg::wrn(_("%s video stream %d: Unsupported stereo layout %s."),
_url.c_str(), index + 1, str::sanitize(val).c_str());
video_frame_template.stereo_layout = parameters::layout_mono;
video_frame_template.stereo_layout_swap = false;
}
/* Sanity checks. If these fail, use safe fallback */
if (((video_frame_template.stereo_layout == parameters::layout_left_right
|| video_frame_template.stereo_layout == parameters::layout_left_right_half)
&& video_frame_template.raw_width % 2 != 0)
|| ((video_frame_template.stereo_layout == parameters::layout_top_bottom
|| video_frame_template.stereo_layout == parameters::layout_top_bottom_half)
&& video_frame_template.raw_height % 2 != 0)
|| (video_frame_template.stereo_layout == parameters::layout_even_odd_rows
&& video_frame_template.raw_height % 2 != 0))
{
video_frame_template.stereo_layout = parameters::layout_mono;
video_frame_template.stereo_layout_swap = false;
}
/* Set width and height of a single view */
video_frame_template.set_view_dimensions();
}
void media_object::set_audio_blob_template(int index)
{
AVStream *audio_stream = _ffmpeg->format_ctx->streams[_ffmpeg->audio_streams[index]];
AVCodecContext *audio_codec_ctx = _ffmpeg->audio_codec_ctxs[index];
audio_blob &audio_blob_template = _ffmpeg->audio_blob_templates[index];
AVDictionaryEntry *tag = av_dict_get(audio_stream->metadata, "language", NULL, AV_DICT_IGNORE_SUFFIX);
if (tag)
{
audio_blob_template.language = tag->value;
}
if (audio_codec_ctx->channels < 1
|| audio_codec_ctx->channels > 8
|| audio_codec_ctx->channels == 3
|| audio_codec_ctx->channels == 5)
{
throw exc(str::asprintf(_("%s audio stream %d: Cannot handle audio with %d channels."),
_url.c_str(), index + 1, audio_codec_ctx->channels));
}
audio_blob_template.channels = audio_codec_ctx->channels;
audio_blob_template.rate = audio_codec_ctx->sample_rate;
if (audio_codec_ctx->sample_fmt == AV_SAMPLE_FMT_U8)
{
audio_blob_template.sample_format = audio_blob::u8;
}
else if (audio_codec_ctx->sample_fmt == AV_SAMPLE_FMT_S16)
{
audio_blob_template.sample_format = audio_blob::s16;
}
else if (audio_codec_ctx->sample_fmt == AV_SAMPLE_FMT_FLT)
{
audio_blob_template.sample_format = audio_blob::f32;
}
else if (audio_codec_ctx->sample_fmt == AV_SAMPLE_FMT_DBL)
{
audio_blob_template.sample_format = audio_blob::d64;
}
else if (audio_codec_ctx->sample_fmt == AV_SAMPLE_FMT_S32
&& sizeof(int32_t) == sizeof(float))
{
// we need to convert this to AV_SAMPLE_FMT_FLT after decoding
audio_blob_template.sample_format = audio_blob::f32;
}
else
{
throw exc(str::asprintf(_("%s audio stream %d: Cannot handle audio with sample format %s."),
_url.c_str(), index + 1, av_get_sample_fmt_name(audio_codec_ctx->sample_fmt)));
}
}
void media_object::set_subtitle_box_template(int index)
{
AVStream *subtitle_stream = _ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[index]];
//AVCodecContext *subtitle_codec_ctx = _ffmpeg->subtitle_codec_ctxs[index];
subtitle_box &subtitle_box_template = _ffmpeg->subtitle_box_templates[index];
AVDictionaryEntry *tag = av_dict_get(subtitle_stream->metadata, "language", NULL, AV_DICT_IGNORE_SUFFIX);
if (tag)
{
subtitle_box_template.language = tag->value;
}
}
void media_object::open(const std::string &url, const device_request &dev_request)
{
assert(!_ffmpeg);
_url = url;
_is_device = dev_request.is_device();
_ffmpeg = new struct ffmpeg_stuff;
_ffmpeg->format_ctx = NULL;
_ffmpeg->have_active_audio_stream = false;
_ffmpeg->pos = 0;
_ffmpeg->reader = new read_thread(_url, _is_device, _ffmpeg);
int e;
/* Set format and parameters for device input */
AVInputFormat *iformat = NULL;
AVDictionary *iparams = NULL;
switch (dev_request.device)
{
case device_request::firewire:
iformat = av_find_input_format("libdc1394");
break;
case device_request::x11:
iformat = av_find_input_format("x11grab");
break;
case device_request::sys_default:
#if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
iformat = av_find_input_format("vfwcap");
#elif defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __APPLE__
iformat = av_find_input_format("bktr");
#else
iformat = av_find_input_format("video4linux2");
#endif
break;
case device_request::no_device:
/* Force the format for a few file types that are unknown to older
* versions of FFmpeg and to Libav. Note: this may be removed in
* the future if all relevant FFmpeg/Libav versions recognize these
* files automatically.
* This fixes the problems for MPO and JPS images listed here:
* http://lists.nongnu.org/archive/html/bino-list/2012-03/msg00026.html
* This does not fix the PNS problem, because the format is "image2" but
* we also need to tell FFmpeg about the codec (PNG), which does not
* seem to be possible.
*/
{
std::string extension = get_extension(_url);
if (extension == "mpo" || extension == "jps")
iformat = av_find_input_format("mjpeg");
}
break;
}
if (_is_device && !iformat)
{
throw exc(str::asprintf(_("No support available for %s device."),
dev_request.device == device_request::firewire ? _("Firewire")
: dev_request.device == device_request::x11 ? _("X11")
: _("default")));
}
if (_is_device && dev_request.width != 0 && dev_request.height != 0)
{
av_dict_set(&iparams, "video_size", str::asprintf("%dx%d",
dev_request.width, dev_request.height).c_str(), 0);
}
if (_is_device && dev_request.frame_rate_num != 0 && dev_request.frame_rate_den != 0)
{
av_dict_set(&iparams, "framerate", str::asprintf("%d/%d",
dev_request.frame_rate_num, dev_request.frame_rate_den).c_str(), 0);
}
if (_is_device && dev_request.request_mjpeg)
{
av_dict_set(&iparams, "input_format", "mjpeg", 0);
}
/* Open the input */
_ffmpeg->format_ctx = NULL;
if ((e = avformat_open_input(&_ffmpeg->format_ctx, _url.c_str(), iformat, &iparams)) != 0)
{
av_dict_free(&iparams);
throw exc(str::asprintf(_("%s: %s"),
_url.c_str(), my_av_strerror(e).c_str()));
}
av_dict_free(&iparams);
if (_is_device)
{
// For a camera device, do not read ahead multiple packets, to avoid a startup delay.
_ffmpeg->format_ctx->max_analyze_duration = 0;
}
if ((e = avformat_find_stream_info(_ffmpeg->format_ctx,NULL)) < 0)
{
throw exc(str::asprintf(_("%s: Cannot read stream info: %s"),
_url.c_str(), my_av_strerror(e).c_str()));
}
av_dump_format(_ffmpeg->format_ctx, 0, _url.c_str(), 0);
/* Metadata */
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(_ffmpeg->format_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
{
_tag_names.push_back(tag->key);
_tag_values.push_back(tag->value);
}
_ffmpeg->have_active_audio_stream = false;
_ffmpeg->pos = std::numeric_limits::min();
for (unsigned int i = 0; i < _ffmpeg->format_ctx->nb_streams
&& i < static_cast(std::numeric_limits::max()); i++)
{
_ffmpeg->format_ctx->streams[i]->discard = AVDISCARD_ALL; // ignore by default; user must activate streams
AVCodecContext *codec_ctx = _ffmpeg->format_ctx->streams[i]->codec;
AVCodec *codec = (codec_ctx->codec_id == CODEC_ID_TEXT
? NULL : avcodec_find_decoder(codec_ctx->codec_id));
// XXX: Sometimes the reported width and height for a video stream change after avcodec_open(),
// but the original values seem to be correct. This seems to happen mostly with 1920x1080 video
// that later is reported as 1920x1088, which results in a gray bar displayed at the bottom of
// the frame. FFplay is also affected. As a workaround, we keep the original values here and use
// them later in set_video_frame_template().
int width_before_avcodec_open = codec_ctx->width;
int height_before_avcodec_open = codec_ctx->height;
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
// Activate multithreaded decoding. This must be done before opening the codec; see
// http://lists.gnu.org/archive/html/bino-list/2011-08/msg00019.html
codec_ctx->thread_count = video_decoding_threads();
// Set CODEC_FLAG_EMU_EDGE in the same situations in which ffplay sets it.
// I don't know what exactly this does, but it is necessary to fix the problem
// described in this thread: http://lists.nongnu.org/archive/html/bino-list/2012-02/msg00039.html
if (codec_ctx->lowres || (codec && (codec->capabilities & CODEC_CAP_DR1)))
codec_ctx->flags |= CODEC_FLAG_EMU_EDGE;
}
// Find and open the codec. CODEC_ID_TEXT is a special case: it has no decoder since it is unencoded raw data.
if (codec_ctx->codec_id != CODEC_ID_TEXT && (!codec || (e = avcodec_open2(codec_ctx, codec,NULL)) < 0))
{
msg::wrn(_("%s stream %d: Cannot open %s: %s"), _url.c_str(), i,
codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ? _("video codec")
: codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO ? _("audio codec")
: codec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE ? _("subtitle codec")
: _("data"),
codec ? my_av_strerror(e).c_str() : _("codec not supported"));
}
else if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
_ffmpeg->video_streams.push_back(i);
int j = _ffmpeg->video_streams.size() - 1;
msg::dbg(_url + " stream " + str::from(i) + " is video stream " + str::from(j) + ".");
_ffmpeg->video_codec_ctxs.push_back(codec_ctx);
if (_ffmpeg->video_codec_ctxs[j]->width < 1 || _ffmpeg->video_codec_ctxs[j]->height < 1)
{
throw exc(str::asprintf(_("%s video stream %d: Invalid frame size."),
_url.c_str(), j + 1));
}
_ffmpeg->video_codecs.push_back(codec);
// Allocate things required for decoding
_ffmpeg->video_packets.push_back(AVPacket());
av_init_packet(&(_ffmpeg->video_packets[j]));
_ffmpeg->video_decode_threads.push_back(video_decode_thread(_url, _ffmpeg, j));
_ffmpeg->video_filter_threads.push_back(video_filter_thread(_ffmpeg,j));
_ffmpeg->video_frames.push_back(avcodec_alloc_frame());
_ffmpeg->video_buffered_frames.push_back(avcodec_alloc_frame());
_ffmpeg->filtering = (blixfilter[0] != 0);
_ffmpeg->video_frame_templates.push_back(video_frame());
if (!_ffmpeg->filtering)
{
set_video_frame_template(j, width_before_avcodec_open, height_before_avcodec_open);
}
else
{
// here is where we allocate the new filter for this stream
bool once = (blixfilter[0] != 0); // filter not empty
while (once)
{
once = false;
AVFilterGraph *filter_graph;
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVCodecContext *dec_ctx = codec_ctx;
char args[512];
int ret;
AVFilter *buffersrc = avfilter_get_by_name("buffer");
AVFilter *buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVBufferSinkParams *buffersink_params = av_buffersink_params_alloc();
enum PixelFormat pix_fmts[] = { dec_ctx->pix_fmt,PIX_FMT_BGRA,PIX_FMT_NONE };
filter_graph = avfilter_graph_alloc();
/* snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,1,1);
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
*/
snprintf(args, sizeof(args), "%d:%d:%d:%d:%d:%d:%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",args, NULL, filter_graph);
if (ret < 0) {
msg::wrn(_("avfilter_graph_create_filter src ref:%d args %s result %d"),buffersrc,args,ret);
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, pix_fmts, filter_graph);
if (ret < 0) {
msg::wrn(_("avfilter_graph_create_filter sink %d"),ret);
}
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
ret = avfilter_graph_parse(filter_graph, blixfilter,
&inputs, &outputs, NULL);
msg::inf(_("avfilter_graph_%s parse returned %d"),blixfilter, ret);
ret = avfilter_graph_config(filter_graph, NULL);
if ( ret < 0) {
msg::wrn(_("avfilter_graph_config returned %d"),ret);
}
_ffmpeg->buffersrc_ctxs.push_back(buffersrc_ctx);
_ffmpeg->buffersink_ctxs.push_back(buffersink_ctx);
// Determine frame template.
set_video_frame_template(j, width_before_avcodec_open, height_before_avcodec_open);
}
} // filtering
enum PixelFormat frame_fmt = (_ffmpeg->video_frame_templates[j].layout == video_frame::bgra32
? PIX_FMT_BGRA : _ffmpeg->video_codec_ctxs[j]->pix_fmt);
int frame_bufsize = (avpicture_get_size(frame_fmt,
_ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height));
_ffmpeg->video_buffers.push_back(static_cast(av_malloc(frame_bufsize)));
avpicture_fill(reinterpret_cast(_ffmpeg->video_buffered_frames[j]), _ffmpeg->video_buffers[j],
frame_fmt, _ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height);
if (!_ffmpeg->video_frames[j] || !_ffmpeg->video_buffered_frames[j] || !_ffmpeg->video_buffers[j])
{
throw exc(HERE + ": " + strerror(ENOMEM));
}
if (_ffmpeg->video_frame_templates[j].layout == video_frame::bgra32)
{
// Initialize things needed for software pixel format conversion
int sws_bufsize = avpicture_get_size(PIX_FMT_BGRA,
_ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height);
_ffmpeg->video_sws_frames.push_back(avcodec_alloc_frame());
_ffmpeg->video_sws_buffers.push_back(static_cast(av_malloc(sws_bufsize)));
if (!_ffmpeg->video_sws_frames[j] || !_ffmpeg->video_sws_buffers[j])
{
throw exc(HERE + ": " + strerror(ENOMEM));
}
avpicture_fill(reinterpret_cast(_ffmpeg->video_sws_frames[j]), _ffmpeg->video_sws_buffers[j],
PIX_FMT_BGRA, _ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height);
// Call sws_getCachedContext(NULL, ...) instead of sws_getContext(...) just to avoid a deprecation warning.
_ffmpeg->video_sws_ctxs.push_back(sws_getCachedContext(NULL,
_ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height, _ffmpeg->video_codec_ctxs[j]->pix_fmt,
_ffmpeg->video_codec_ctxs[j]->width, _ffmpeg->video_codec_ctxs[j]->height, PIX_FMT_BGRA,
SWS_POINT, NULL, NULL, NULL));
if (!_ffmpeg->video_sws_ctxs[j])
{
throw exc(str::asprintf(_("%s video stream %d: Cannot initialize conversion context."),
_url.c_str(), j + 1));
}
}
else
{
_ffmpeg->video_sws_frames.push_back(NULL);
_ffmpeg->video_sws_buffers.push_back(NULL);
_ffmpeg->video_sws_ctxs.push_back(NULL);
}
_ffmpeg->video_last_timestamps.push_back(std::numeric_limits::min());
}
else if (codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
_ffmpeg->audio_streams.push_back(i);
int j = _ffmpeg->audio_streams.size() - 1;
msg::dbg(_url + " stream " + str::from(i) + " is audio stream " + str::from(j) + ".");
_ffmpeg->audio_codec_ctxs.push_back(codec_ctx);
_ffmpeg->audio_codecs.push_back(codec);
_ffmpeg->audio_blob_templates.push_back(audio_blob());
set_audio_blob_template(j);
_ffmpeg->audio_decode_threads.push_back(audio_decode_thread(_url, _ffmpeg, j));
// Manage audio_tmpbufs with av_malloc/av_free, to guarantee correct alignment.
// Not doing this results in hard to debug crashes on some systems.
_ffmpeg->audio_tmpbufs.push_back(static_cast(av_malloc(audio_tmpbuf_size)));
if (!_ffmpeg->audio_tmpbufs[j])
{
throw exc(HERE + ": " + strerror(ENOMEM));
}
_ffmpeg->audio_blobs.push_back(blob());
_ffmpeg->audio_buffers.push_back(std::vector());
_ffmpeg->audio_last_timestamps.push_back(std::numeric_limits::min());
}
else if (codec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE)
{
_ffmpeg->subtitle_streams.push_back(i);
int j = _ffmpeg->subtitle_streams.size() - 1;
msg::dbg(_url + " stream " + str::from(i) + " is subtitle stream " + str::from(j) + ".");
_ffmpeg->subtitle_codec_ctxs.push_back(codec_ctx);
// CODEC_ID_TEXT does not have any decoder; it is just UTF-8 text in the packet data.
_ffmpeg->subtitle_codecs.push_back(
_ffmpeg->subtitle_codec_ctxs[j]->codec_id == CODEC_ID_TEXT ? NULL : codec);
_ffmpeg->subtitle_box_templates.push_back(subtitle_box());
set_subtitle_box_template(j);
_ffmpeg->subtitle_decode_threads.push_back(subtitle_decode_thread(_url, _ffmpeg, j));
_ffmpeg->subtitle_box_buffers.push_back(std::deque());
_ffmpeg->subtitle_last_timestamps.push_back(std::numeric_limits::min());
}
else
{
msg::dbg(_url + " stream " + str::from(i) + " contains neither video nor audio nor subtitles.");
}
}
_ffmpeg->video_packet_queues.resize(video_streams());
_ffmpeg->video_frame_queues.resize(video_streams());
_ffmpeg->video_frame_presentation_times.resize(video_streams());
_ffmpeg->audio_packet_queues.resize(audio_streams());
_ffmpeg->subtitle_packet_queues.resize(subtitle_streams());
_ffmpeg->video_packet_queue_mutexes.resize(video_streams());
_ffmpeg->video_frame_queue_mutexes.resize(video_streams());
_ffmpeg->audio_packet_queue_mutexes.resize(audio_streams());
_ffmpeg->subtitle_packet_queue_mutexes.resize(subtitle_streams());
msg::inf(_url + ":");
for (int i = 0; i < video_streams(); i++)
{
msg::inf(4, _("Video stream %d: %s / %s, %g seconds"), i,
video_frame_template(i).format_info().c_str(),
video_frame_template(i).format_name().c_str(),
video_duration(i) / 1e6f);
msg::inf(8, _("Using up to %d threads for decoding."),
_ffmpeg->video_codec_ctxs.at(i)->thread_count);
}
for (int i = 0; i < audio_streams(); i++)
{
msg::inf(4, _("Audio stream %d: %s / %s, %g seconds"), i,
audio_blob_template(i).format_info().c_str(),
audio_blob_template(i).format_name().c_str(),
audio_duration(i) / 1e6f);
}
for (int i = 0; i < subtitle_streams(); i++)
{
msg::inf(4, _("Subtitle stream %d: %s / %s, %g seconds"), i,
subtitle_box_template(i).format_info().c_str(),
subtitle_box_template(i).format_name().c_str(),
subtitle_duration(i) / 1e6f);
}
if (video_streams() == 0 && audio_streams() == 0 && subtitle_streams() == 0)
{
msg::inf(4, _("No usable streams."));
}
}
const std::string &media_object::url() const
{
return _url;
}
size_t media_object::tags() const
{
return _tag_names.size();
}
const std::string &media_object::tag_name(size_t i) const
{
assert(i < tags());
return _tag_names[i];
}
const std::string &media_object::tag_value(size_t i) const
{
assert(i < tags());
return _tag_values[i];
}
const std::string &media_object::tag_value(const std::string &tag_name) const
{
static std::string empty;
for (size_t i = 0; i < _tag_names.size(); i++)
{
if (std::string(tag_name) == _tag_names[i])
{
return _tag_values[i];
}
}
return empty;
}
int media_object::video_streams() const
{
return _ffmpeg->video_streams.size();
}
int media_object::audio_streams() const
{
return _ffmpeg->audio_streams.size();
}
int media_object::subtitle_streams() const
{
return _ffmpeg->subtitle_streams.size();
}
void media_object::video_stream_set_active(int index, bool active)
{
assert(index >= 0);
assert(index < video_streams());
// Stop decoder threads
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_filter_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
_ffmpeg->audio_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
_ffmpeg->subtitle_decode_threads[i].finish();
}
// Stop reading packets
_ffmpeg->reader->finish();
// Set status
_ffmpeg->format_ctx->streams[_ffmpeg->video_streams.at(index)]->discard =
(active ? AVDISCARD_DEFAULT : AVDISCARD_ALL);
// Restart reader
_ffmpeg->reader->start();
}
void media_object::audio_stream_set_active(int index, bool active)
{
assert(index >= 0);
assert(index < audio_streams());
// Stop decoder threads
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
_ffmpeg->audio_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
_ffmpeg->subtitle_decode_threads[i].finish();
}
// Stop reading packets
_ffmpeg->reader->finish();
// Set status
_ffmpeg->format_ctx->streams[_ffmpeg->audio_streams.at(index)]->discard =
(active ? AVDISCARD_DEFAULT : AVDISCARD_ALL);
_ffmpeg->have_active_audio_stream = false;
for (int i = 0; i < audio_streams(); i++)
{
if (_ffmpeg->format_ctx->streams[_ffmpeg->audio_streams.at(index)]->discard == AVDISCARD_DEFAULT)
{
_ffmpeg->have_active_audio_stream = true;
break;
}
}
// Restart reader
_ffmpeg->reader->start();
}
void media_object::subtitle_stream_set_active(int index, bool active)
{
assert(index >= 0);
assert(index < subtitle_streams());
// Stop decoder threads
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
_ffmpeg->audio_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
_ffmpeg->subtitle_decode_threads[i].finish();
}
// Stop reading packets
_ffmpeg->reader->finish();
// Set status
_ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams.at(index)]->discard =
(active ? AVDISCARD_DEFAULT : AVDISCARD_ALL);
// Restart reader
_ffmpeg->reader->start();
}
const video_frame &media_object::video_frame_template(int video_stream) const
{
assert(video_stream >= 0);
assert(video_stream < video_streams());
return _ffmpeg->video_frame_templates.at(video_stream);
}
int media_object::video_frame_rate_numerator(int index) const
{
assert(index >= 0);
assert(index < video_streams());
return _ffmpeg->format_ctx->streams[_ffmpeg->video_streams.at(index)]->r_frame_rate.num;
}
int media_object::video_frame_rate_denominator(int index) const
{
assert(index >= 0);
assert(index < video_streams());
return _ffmpeg->format_ctx->streams[_ffmpeg->video_streams.at(index)]->r_frame_rate.den;
}
int64_t media_object::video_duration(int index) const
{
assert(index >= 0);
assert(index < video_streams());
return stream_duration(
_ffmpeg->format_ctx->streams[_ffmpeg->video_streams.at(index)],
_ffmpeg->format_ctx);
}
const audio_blob &media_object::audio_blob_template(int audio_stream) const
{
assert(audio_stream >= 0);
assert(audio_stream < audio_streams());
return _ffmpeg->audio_blob_templates.at(audio_stream);
}
int64_t media_object::audio_duration(int index) const
{
assert(index >= 0);
assert(index < audio_streams());
return stream_duration(
_ffmpeg->format_ctx->streams[_ffmpeg->audio_streams.at(index)],
_ffmpeg->format_ctx);
}
const subtitle_box &media_object::subtitle_box_template(int subtitle_stream) const
{
assert(subtitle_stream >= 0);
assert(subtitle_stream < subtitle_streams());
return _ffmpeg->subtitle_box_templates.at(subtitle_stream);
}
int64_t media_object::subtitle_duration(int index) const
{
assert(index >= 0);
assert(index < subtitle_streams());
return stream_duration(
_ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams.at(index)],
_ffmpeg->format_ctx);
}
read_thread::read_thread(const std::string &url, bool is_device, struct ffmpeg_stuff *ffmpeg) :
_url(url), _is_device(is_device), _ffmpeg(ffmpeg), _eof(false)
{
}
void read_thread::run()
{
while (!_eof)
{
// We need another packet if the number of queued packets for an active stream is below a threshold.
// For files, we often want to read ahead to avoid i/o waits. For devices, we do not want to read
// ahead to avoid latency.
const size_t video_stream_low_threshold = (_is_device ? 1 : 2); // Often, 1 packet results in one video frame
const size_t audio_stream_low_threshold = (_is_device ? 1 : 5); // Often, 3-4 packets are needed for one buffer fill
const size_t subtitle_stream_low_threshold = (_is_device ? 1 : 1); // Just a guess
bool need_another_packet = false;
for (size_t i = 0; !need_another_packet && i < _ffmpeg->video_streams.size(); i++)
{
if (_ffmpeg->format_ctx->streams[_ffmpeg->video_streams[i]]->discard == AVDISCARD_DEFAULT)
{
_ffmpeg->video_packet_queue_mutexes[i].lock();
need_another_packet = _ffmpeg->video_packet_queues[i].size() < video_stream_low_threshold;
_ffmpeg->video_packet_queue_mutexes[i].unlock();
}
}
for (size_t i = 0; !need_another_packet && i < _ffmpeg->audio_streams.size(); i++)
{
if (_ffmpeg->format_ctx->streams[_ffmpeg->audio_streams[i]]->discard == AVDISCARD_DEFAULT)
{
_ffmpeg->audio_packet_queue_mutexes[i].lock();
need_another_packet = _ffmpeg->audio_packet_queues[i].size() < audio_stream_low_threshold;
_ffmpeg->audio_packet_queue_mutexes[i].unlock();
} }
for (size_t i = 0; !need_another_packet && i < _ffmpeg->subtitle_streams.size(); i++)
{
if (_ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[i]]->discard == AVDISCARD_DEFAULT)
{
_ffmpeg->subtitle_packet_queue_mutexes[i].lock();
need_another_packet = _ffmpeg->subtitle_packet_queues[i].size() < subtitle_stream_low_threshold;
_ffmpeg->subtitle_packet_queue_mutexes[i].unlock();
}
}
if (!need_another_packet)
{
msg::dbg(_url + ": No need to read more packets.");
break;
}
// Read a packet.
msg::dbg(_url + ": Reading a packet.");
AVPacket packet;
int e = av_read_frame(_ffmpeg->format_ctx, &packet);
if (e < 0)
{
if (e == AVERROR_EOF)
{
msg::dbg(_url + ": EOF.");
_eof = true;
return;
}
else
{
throw exc(str::asprintf(_("%s: %s"), _url.c_str(), my_av_strerror(e).c_str()));
}
}
// Put the packet in the right queue.
bool packet_queued = false;
for (size_t i = 0; i < _ffmpeg->video_streams.size() && !packet_queued; i++)
{
if (packet.stream_index == _ffmpeg->video_streams[i])
{
// We do not check for missing timestamps here, as we do with audio
// packets, for the following reasons:
// 1. The video decoder might fill in a timestamp for us
// 2. We cannot drop video packets anyway, because of their
// interdependencies. We would mess up decoding.
if (av_dup_packet(&packet) < 0)
{
throw exc(str::asprintf(_("%s: Cannot duplicate packet."), _url.c_str()));
}
_ffmpeg->video_packet_queue_mutexes[i].lock();
_ffmpeg->video_packet_queues[i].push_back(packet);
_ffmpeg->video_packet_queue_mutexes[i].unlock();
packet_queued = true;
msg::dbg(_url + ": "
+ str::from(_ffmpeg->video_packet_queues[i].size())
+ " packets queued in video stream " + str::from(i) + ".");
}
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size() && !packet_queued; i++)
{
if (packet.stream_index == _ffmpeg->audio_streams[i])
{
_ffmpeg->audio_packet_queue_mutexes[i].lock();
if (_ffmpeg->audio_packet_queues[i].empty()
&& _ffmpeg->audio_last_timestamps[i] == std::numeric_limits::min()
&& packet.dts == static_cast(AV_NOPTS_VALUE))
{
// We have no packet in the queue and no last timestamp, probably
// because we just seeked. We *need* a packet with a timestamp.
msg::dbg(_url + ": audio stream " + str::from(i)
+ ": dropping packet because it has no timestamp");
}
else
{
if (av_dup_packet(&packet) < 0)
{
_ffmpeg->audio_packet_queue_mutexes[i].unlock();
throw exc(str::asprintf(_("%s: Cannot duplicate packet."), _url.c_str()));
}
_ffmpeg->audio_packet_queues[i].push_back(packet);
packet_queued = true;
msg::dbg(_url + ": "
+ str::from(_ffmpeg->audio_packet_queues[i].size())
+ " packets queued in audio stream " + str::from(i) + ".");
}
_ffmpeg->audio_packet_queue_mutexes[i].unlock();
}
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size() && !packet_queued; i++)
{
if (packet.stream_index == _ffmpeg->subtitle_streams[i])
{
_ffmpeg->subtitle_packet_queue_mutexes[i].lock();
if (_ffmpeg->subtitle_packet_queues[i].empty()
&& _ffmpeg->subtitle_last_timestamps[i] == std::numeric_limits::min()
&& packet.dts == static_cast(AV_NOPTS_VALUE))
{
// We have no packet in the queue and no last timestamp, probably
// because we just seeked. We want a packet with a timestamp.
msg::dbg(_url + ": subtitle stream " + str::from(i)
+ ": dropping packet because it has no timestamp");
}
else
{
if (av_dup_packet(&packet) < 0)
{
_ffmpeg->subtitle_packet_queue_mutexes[i].unlock();
throw exc(str::asprintf(_("%s: Cannot duplicate packet."), _url.c_str()));
}
_ffmpeg->subtitle_packet_queues[i].push_back(packet);
packet_queued = true;
msg::dbg(_url + ": "
+ str::from(_ffmpeg->subtitle_packet_queues[i].size())
+ " packets queued in subtitle stream " + str::from(i) + ".");
}
_ffmpeg->subtitle_packet_queue_mutexes[i].unlock();
}
}
if (!packet_queued)
{
av_free_packet(&packet);
}
}
}
void read_thread::reset()
{
exception() = exc();
_eof = false;
}
video_decode_thread::video_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int video_stream) :
_url(url), _eof(false), _ffmpeg(ffmpeg), _video_stream(video_stream), _raw_frames(1)
{
}
int64_t video_decode_thread::handle_timestamp(int64_t timestamp)
{
int64_t ts = timestamp_helper(_ffmpeg->video_last_timestamps[_video_stream], timestamp);
if (!_ffmpeg->have_active_audio_stream || _ffmpeg->pos == std::numeric_limits::min())
{
_ffmpeg->pos = ts;
}
return ts;
}
void video_decode_thread::run()
{
// The purpose of the exercise is to populate _frame with the next frame of video possibly after passing it through a filter
int frame_finished = 0;
AVFrame *theFrame = avcodec_alloc_frame();
AVFilterBufferRef *picref = NULL;
bool hadPendingFrame=false;
if (_ffmpeg->filtering )
{
int ret = avfilter_poll_frame( _ffmpeg->buffersink_ctxs[_video_stream]->inputs[0]);
if (ret > 0) { // we have a pending frame in the buffer already
av_vsink_buffer_get_video_buffer_ref(_ffmpeg->buffersink_ctxs[_video_stream],&picref,0);
avfilter_fill_frame_from_video_buffer_ref(theFrame,picref);
avfilter_unref_buffer(picref);
hadPendingFrame=true;
}
}
if (!hadPendingFrame)
{
read_frame:
do
{
bool empty;
do
{
_ffmpeg->video_packet_queue_mutexes[_video_stream].lock();
empty = _ffmpeg->video_packet_queues[_video_stream].empty();
_ffmpeg->video_packet_queue_mutexes[_video_stream].unlock();
if (empty)
{
if (_ffmpeg->reader->eof())
{
_eof = true;
return;
}
msg::dbg(_url + ": video stream " + str::from(_video_stream) + ": need to wait for packets...");
_ffmpeg->reader->start();
_ffmpeg->reader->finish();
}
}
while (empty);
av_free_packet(&(_ffmpeg->video_packets[_video_stream]));
_ffmpeg->video_packet_queue_mutexes[_video_stream].lock();
_ffmpeg->video_packets[_video_stream] = _ffmpeg->video_packet_queues[_video_stream].front();
_ffmpeg->video_packet_queues[_video_stream].pop_front();
_ffmpeg->video_packet_queue_mutexes[_video_stream].unlock();
_ffmpeg->reader->start(); // Refill the packet queue
avcodec_decode_video2(_ffmpeg->video_codec_ctxs[_video_stream],
theFrame, &frame_finished,
&(_ffmpeg->video_packets[_video_stream]));
}
while (!frame_finished);
#if LIBAVCODEC_VERSION_MAJOR >= 53 && LIBAVCODEC_VERSION_MINOR >= 8
if (theFrame->width != _ffmpeg->video_frame_templates[_video_stream].raw_width
|| theFrame->height != _ffmpeg->video_frame_templates[_video_stream].raw_height)
{
msg::wrn(_("%s video stream %d: Dropping %dx%d frame"), _url.c_str(), _video_stream + 1,
theFrame->width, theFrame->height);
goto read_frame;
}
#endif
// At this point we have successfully decoded a frame we like but we may need to pass it through the filter....
if (_ffmpeg->filtering)
{
// we are filtering so do our new stuff
int ret;
// Add the new frame to the filter input
av_vsrc_buffer_add_frame(_ffmpeg->buffersrc_ctxs[_video_stream], theFrame,0);
ret = avfilter_poll_frame( _ffmpeg->buffersink_ctxs[_video_stream]->inputs[0]);
if (ret < 0) {
msg::wrn("poll frame error %d",ret);
goto read_frame;
}
if ( ret == 0) {
goto read_frame;
}
ret = av_vsink_buffer_get_video_buffer_ref(_ffmpeg->buffersink_ctxs[_video_stream],&picref,0);
if ( ret < 0 || picref == NULL) {
goto read_frame;
}
avfilter_fill_frame_from_video_buffer_ref(theFrame,picref);
avfilter_unref_buffer(picref);
}
}
// timestamp stuff - if we filter this is a bit wrong because we are using the time from
// the most recent packet used in the filter. Is there are better way?
int64_t presentation_time;
if (_ffmpeg->video_packets[_video_stream].dts != static_cast(AV_NOPTS_VALUE))
{
presentation_time = handle_timestamp(_ffmpeg->video_packets[_video_stream].dts * 1000000
* _ffmpeg->format_ctx->streams[_ffmpeg->video_streams[_video_stream]]->time_base.num
/ _ffmpeg->format_ctx->streams[_ffmpeg->video_streams[_video_stream]]->time_base.den);
}
else if (_ffmpeg->video_last_timestamps[_video_stream] != std::numeric_limits::min())
{
msg::dbg(_url + ": video stream " + str::from(_video_stream)
+ ": no timestamp available, using a questionable guess");
presentation_time = _ffmpeg->video_last_timestamps[_video_stream];
}
else
{
msg::dbg(_url + ": video stream " + str::from(_video_stream)
+ ": no timestamp available, using a bad guess");
presentation_time = _ffmpeg->pos;
}
_ffmpeg->video_frame_queue_mutexes[_video_stream].lock();
_ffmpeg->video_frame_queues[_video_stream].push_back(theFrame);
_ffmpeg->video_frame_presentation_times[_video_stream].push_back(presentation_time);
_ffmpeg->video_frame_queue_mutexes[_video_stream].unlock();
}
video_filter_thread::video_filter_thread(struct ffmpeg_stuff *ffmpeg, int video_stream) :
_ffmpeg(ffmpeg), _video_stream(video_stream), _frame(), _raw_frames(1)
{
}
int64_t video_filter_thread::handle_timestamp(int64_t timestamp)
{
int64_t ts = timestamp_helper(_ffmpeg->video_last_timestamps[_video_stream], timestamp);
if (!_ffmpeg->have_active_audio_stream || _ffmpeg->pos == std::numeric_limits::min())
{
_ffmpeg->pos = ts;
}
return ts;
}
void video_filter_thread::run()
{
_frame = _ffmpeg->video_frame_templates[_video_stream];
AVFrame *src_frame;
int64_t presentation_time;
for (int raw_frame = 0; raw_frame < _raw_frames; raw_frame++)
{
read_frame:
bool empty;
do
{
_ffmpeg->video_frame_queue_mutexes[_video_stream].lock();
empty = _ffmpeg->video_frame_queues[_video_stream].empty();
_ffmpeg->video_frame_queue_mutexes[_video_stream].unlock();
if (empty)
{
if (_ffmpeg->video_decode_threads[_video_stream].eof())
{
if (raw_frame == 1)
{
_frame.data[1][0] = _frame.data[0][0];
_frame.data[1][1] = _frame.data[0][1];
_frame.data[1][2] = _frame.data[0][2];
_frame.line_size[1][0] = _frame.line_size[0][0];
_frame.line_size[1][1] = _frame.line_size[0][1];
_frame.line_size[1][2] = _frame.line_size[0][2];
}
else
{
_frame = video_frame();
}
return;
}
msg::dbg(": video stream " + str::from(_video_stream) + ": need to wait for packets...");
_ffmpeg->video_decode_threads[_video_stream].start();
_ffmpeg->video_decode_threads[_video_stream].finish();
}
}
while (empty);
_ffmpeg->video_frame_queue_mutexes[_video_stream].lock();
presentation_time = _ffmpeg->video_frame_presentation_times[_video_stream].front();
_ffmpeg->video_frame_presentation_times[_video_stream].pop_front();
src_frame = _ffmpeg->video_frame_queues[_video_stream].front();
_ffmpeg->video_frame_queues[_video_stream].pop_front();
_ffmpeg->video_frame_queue_mutexes[_video_stream].unlock();
_ffmpeg->video_decode_threads[_video_stream].start();
//_ffmpeg->video_frames[_video_stream] = src_frame;;
// At this point we have got a frame
// compare _frame with src_frame to ensure consistency
if (_frame.layout == video_frame::bgra32)
{
sws_scale(_ffmpeg->video_sws_ctxs[_video_stream],
src_frame->data,
src_frame->linesize,
0, _frame.raw_height,
_ffmpeg->video_sws_frames[_video_stream]->data,
_ffmpeg->video_sws_frames[_video_stream]->linesize);
// TODO: Handle sws_scale errors. How?
_frame.data[raw_frame][0] = _ffmpeg->video_sws_frames[_video_stream]->data[0];
_frame.line_size[raw_frame][0] = _ffmpeg->video_sws_frames[_video_stream]->linesize[0];
}
else
{
_frame.data[raw_frame][0] = src_frame->data[0];
_frame.data[raw_frame][1] = src_frame->data[1];
_frame.data[raw_frame][2] = src_frame->data[2];
_frame.line_size[raw_frame][0] = src_frame->linesize[0];
_frame.line_size[raw_frame][1] = src_frame->linesize[1];
_frame.line_size[raw_frame][2] = src_frame->linesize[2];
_frame.presentation_time = presentation_time;
av_free(src_frame);
}
}
}
void media_object::start_video_frame_read(int video_stream, int raw_frames)
{
assert(video_stream >= 0);
assert(video_stream < video_streams());
assert(raw_frames == 1 || raw_frames == 2);
_ffmpeg->video_filter_threads[video_stream].set_raw_frames(raw_frames);
_ffmpeg->video_filter_threads[video_stream].start();
}
video_frame media_object::finish_video_frame_read(int video_stream)
{
assert(video_stream >= 0);
assert(video_stream < video_streams());
_ffmpeg->video_filter_threads[video_stream].finish();
return _ffmpeg->video_filter_threads[video_stream].frame();
}
audio_decode_thread::audio_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int audio_stream) :
_url(url), _ffmpeg(ffmpeg), _audio_stream(audio_stream), _blob()
{
}
int64_t audio_decode_thread::handle_timestamp(int64_t timestamp)
{
int64_t ts = timestamp_helper(_ffmpeg->audio_last_timestamps[_audio_stream], timestamp);
_ffmpeg->pos = ts;
return ts;
}
void audio_decode_thread::run()
{
size_t size = _ffmpeg->audio_blobs[_audio_stream].size();
void *buffer = _ffmpeg->audio_blobs[_audio_stream].ptr();
int64_t timestamp = std::numeric_limits::min();
size_t i = 0;
while (i < size)
{
if (_ffmpeg->audio_buffers[_audio_stream].size() > 0)
{
// Use available decoded audio data
size_t remaining = std::min(size - i, _ffmpeg->audio_buffers[_audio_stream].size());
memcpy(buffer, &(_ffmpeg->audio_buffers[_audio_stream][0]), remaining);
_ffmpeg->audio_buffers[_audio_stream].erase(
_ffmpeg->audio_buffers[_audio_stream].begin(),
_ffmpeg->audio_buffers[_audio_stream].begin() + remaining);
buffer = reinterpret_cast(buffer) + remaining;
i += remaining;
}
if (_ffmpeg->audio_buffers[_audio_stream].size() == 0)
{
// Read more audio data
AVPacket packet, tmppacket;
bool empty;
do
{
_ffmpeg->audio_packet_queue_mutexes[_audio_stream].lock();
empty = _ffmpeg->audio_packet_queues[_audio_stream].empty();
_ffmpeg->audio_packet_queue_mutexes[_audio_stream].unlock();
if (empty)
{
if (_ffmpeg->reader->eof())
{
_blob = audio_blob();
return;
}
msg::dbg(_url + ": audio stream " + str::from(_audio_stream) + ": need to wait for packets...");
_ffmpeg->reader->start();
_ffmpeg->reader->finish();
}
}
while (empty);
_ffmpeg->audio_packet_queue_mutexes[_audio_stream].lock();
packet = _ffmpeg->audio_packet_queues[_audio_stream].front();
_ffmpeg->audio_packet_queues[_audio_stream].pop_front();
_ffmpeg->audio_packet_queue_mutexes[_audio_stream].unlock();
_ffmpeg->reader->start(); // Refill the packet queue
if (timestamp == std::numeric_limits::min() && packet.dts != static_cast(AV_NOPTS_VALUE))
{
timestamp = packet.dts * 1000000
* _ffmpeg->format_ctx->streams[_ffmpeg->audio_streams[_audio_stream]]->time_base.num
/ _ffmpeg->format_ctx->streams[_ffmpeg->audio_streams[_audio_stream]]->time_base.den;
}
// Decode audio data
tmppacket = packet;
while (tmppacket.size > 0)
{
int tmpbuf_size = audio_tmpbuf_size;
int len = avcodec_decode_audio3(_ffmpeg->audio_codec_ctxs[_audio_stream],
reinterpret_cast(&(_ffmpeg->audio_tmpbufs[_audio_stream][0])), &tmpbuf_size, &tmppacket);
if (len < 0)
{
tmppacket.size = 0;
break;
}
tmppacket.data += len;
tmppacket.size -= len;
if (tmpbuf_size <= 0)
{
continue;
}
// Put it in the decoded audio data buffer
if (_ffmpeg->audio_codec_ctxs[_audio_stream]->sample_fmt == AV_SAMPLE_FMT_S32)
{
// we need to convert this to AV_SAMPLE_FMT_FLT
assert(sizeof(int32_t) == sizeof(float));
assert(tmpbuf_size % sizeof(int32_t) == 0);
void *tmpbuf_v = static_cast(&(_ffmpeg->audio_tmpbufs[_audio_stream][0]));
int32_t *tmpbuf_i32 = static_cast(tmpbuf_v);
float *tmpbuf_flt = static_cast(tmpbuf_v);
const float posdiv = +static_cast(std::numeric_limits::max());
const float negdiv = -static_cast(std::numeric_limits::min());
for (size_t j = 0; j < tmpbuf_size / sizeof(int32_t); j++)
{
int32_t sample_i32 = tmpbuf_i32[j];
float sample_flt = sample_i32 / (sample_i32 >= 0 ? posdiv : negdiv);
tmpbuf_flt[j] = sample_flt;
}
}
size_t old_size = _ffmpeg->audio_buffers[_audio_stream].size();
_ffmpeg->audio_buffers[_audio_stream].resize(old_size + tmpbuf_size);
memcpy(&(_ffmpeg->audio_buffers[_audio_stream][old_size]), _ffmpeg->audio_tmpbufs[_audio_stream], tmpbuf_size);
}
av_free_packet(&packet);
}
}
if (timestamp == std::numeric_limits::min())
{
timestamp = _ffmpeg->audio_last_timestamps[_audio_stream];
}
if (timestamp == std::numeric_limits::min())
{
msg::dbg(_url + ": audio stream " + str::from(_audio_stream)
+ ": no timestamp available, using a bad guess");
timestamp = _ffmpeg->pos;
}
_blob = _ffmpeg->audio_blob_templates[_audio_stream];
_blob.data = _ffmpeg->audio_blobs[_audio_stream].ptr();
_blob.size = _ffmpeg->audio_blobs[_audio_stream].size();
_blob.presentation_time = handle_timestamp(timestamp);
}
void media_object::start_audio_blob_read(int audio_stream, size_t size)
{
assert(audio_stream >= 0);
assert(audio_stream < audio_streams());
_ffmpeg->audio_blobs[audio_stream].resize(size);
_ffmpeg->audio_decode_threads[audio_stream].start();
}
audio_blob media_object::finish_audio_blob_read(int audio_stream)
{
assert(audio_stream >= 0);
assert(audio_stream < audio_streams());
_ffmpeg->audio_decode_threads[audio_stream].finish();
return _ffmpeg->audio_decode_threads[audio_stream].blob();
}
subtitle_decode_thread::subtitle_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int subtitle_stream) :
_url(url), _ffmpeg(ffmpeg), _subtitle_stream(subtitle_stream), _box()
{
}
int64_t subtitle_decode_thread::handle_timestamp(int64_t timestamp)
{
int64_t ts = timestamp_helper(_ffmpeg->subtitle_last_timestamps[_subtitle_stream], timestamp);
_ffmpeg->pos = ts;
return ts;
}
void subtitle_decode_thread::run()
{
if (_ffmpeg->subtitle_box_buffers[_subtitle_stream].empty())
{
// Read more subtitle data
AVPacket packet, tmppacket;
bool empty;
do
{
_ffmpeg->subtitle_packet_queue_mutexes[_subtitle_stream].lock();
empty = _ffmpeg->subtitle_packet_queues[_subtitle_stream].empty();
_ffmpeg->subtitle_packet_queue_mutexes[_subtitle_stream].unlock();
if (empty)
{
if (_ffmpeg->reader->eof())
{
_box = subtitle_box();
return;
}
msg::dbg(_url + ": subtitle stream " + str::from(_subtitle_stream) + ": need to wait for packets...");
_ffmpeg->reader->start();
_ffmpeg->reader->finish();
}
}
while (empty);
_ffmpeg->subtitle_packet_queue_mutexes[_subtitle_stream].lock();
packet = _ffmpeg->subtitle_packet_queues[_subtitle_stream].front();
_ffmpeg->subtitle_packet_queues[_subtitle_stream].pop_front();
_ffmpeg->subtitle_packet_queue_mutexes[_subtitle_stream].unlock();
_ffmpeg->reader->start(); // Refill the packet queue
// Decode subtitle data
int64_t timestamp = packet.pts * 1000000
* _ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[_subtitle_stream]]->time_base.num
/ _ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[_subtitle_stream]]->time_base.den;
AVSubtitle subtitle;
int got_subtitle;
tmppacket = packet;
// CODEC_ID_TEXT does not have any decoder; it is just UTF-8 text in the packet data.
if (_ffmpeg->subtitle_codec_ctxs[_subtitle_stream]->codec_id == CODEC_ID_TEXT)
{
int64_t duration = packet.convergence_duration * 1000000
* _ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[_subtitle_stream]]->time_base.num
/ _ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[_subtitle_stream]]->time_base.den;
// Put it in the subtitle buffer
subtitle_box box = _ffmpeg->subtitle_box_templates[_subtitle_stream];
box.presentation_start_time = timestamp;
box.presentation_stop_time = timestamp + duration;
box.format = subtitle_box::text;
box.str = reinterpret_cast(packet.data);
_ffmpeg->subtitle_box_buffers[_subtitle_stream].push_back(box);
tmppacket.size = 0;
}
while (tmppacket.size > 0)
{
int len = avcodec_decode_subtitle2(_ffmpeg->subtitle_codec_ctxs[_subtitle_stream],
&subtitle, &got_subtitle, &tmppacket);
if (len < 0)
{
tmppacket.size = 0;
break;
}
tmppacket.data += len;
tmppacket.size -= len;
if (!got_subtitle)
{
continue;
}
// Put it in the subtitle buffer
subtitle_box box = _ffmpeg->subtitle_box_templates[_subtitle_stream];
box.presentation_start_time = timestamp + subtitle.start_display_time * 1000;
box.presentation_stop_time = box.presentation_start_time + subtitle.end_display_time * 1000;
for (unsigned int i = 0; i < subtitle.num_rects; i++)
{
AVSubtitleRect *rect = subtitle.rects[i];
switch (rect->type)
{
case SUBTITLE_BITMAP:
box.format = subtitle_box::image;
box.images.push_back(subtitle_box::image_t());
box.images.back().w = rect->w;
box.images.back().h = rect->h;
box.images.back().x = rect->x;
box.images.back().y = rect->y;
box.images.back().palette.resize(4 * rect->nb_colors);
std::memcpy(&(box.images.back().palette[0]), rect->pict.data[1],
box.images.back().palette.size());
box.images.back().linesize = rect->pict.linesize[0];
box.images.back().data.resize(box.images.back().h * box.images.back().linesize);
std::memcpy(&(box.images.back().data[0]), rect->pict.data[0],
box.images.back().data.size() * sizeof(uint8_t));
break;
case SUBTITLE_TEXT:
box.format = subtitle_box::text;
if (!box.str.empty())
{
box.str += '\n';
}
box.str += rect->text;
break;
case SUBTITLE_ASS:
box.format = subtitle_box::ass;
box.style = std::string(reinterpret_cast(
_ffmpeg->subtitle_codec_ctxs[_subtitle_stream]->subtitle_header),
_ffmpeg->subtitle_codec_ctxs[_subtitle_stream]->subtitle_header_size);
if (!box.str.empty())
{
box.str += '\n';
}
box.str += rect->ass;
break;
case SUBTITLE_NONE:
// Should never happen, but make sure we have a valid subtitle box anyway.
box.format = subtitle_box::text;
box.str = ' ';
break;
}
}
_ffmpeg->subtitle_box_buffers[_subtitle_stream].push_back(box);
avsubtitle_free(&subtitle);
}
av_free_packet(&packet);
}
if (_ffmpeg->subtitle_box_buffers[_subtitle_stream].empty())
{
_box = subtitle_box();
return;
}
_box = _ffmpeg->subtitle_box_buffers[_subtitle_stream].front();
_ffmpeg->subtitle_box_buffers[_subtitle_stream].pop_front();
}
void media_object::start_subtitle_box_read(int subtitle_stream)
{
assert(subtitle_stream >= 0);
assert(subtitle_stream < subtitle_streams());
_ffmpeg->subtitle_decode_threads[subtitle_stream].start();
}
subtitle_box media_object::finish_subtitle_box_read(int subtitle_stream)
{
assert(subtitle_stream >= 0);
assert(subtitle_stream < subtitle_streams());
_ffmpeg->subtitle_decode_threads[subtitle_stream].finish();
return _ffmpeg->subtitle_decode_threads[subtitle_stream].box();
}
int64_t media_object::tell()
{
return _ffmpeg->pos;
}
void media_object::seek(int64_t dest_pos)
{
msg::dbg(_url + ": Seeking from " + str::from(_ffmpeg->pos / 1e6f) + " to " + str::from(dest_pos / 1e6f) + ".");
// Stop decoder threads
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
_ffmpeg->audio_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
_ffmpeg->subtitle_decode_threads[i].finish();
}
// Stop reading packets
_ffmpeg->reader->finish();
// Throw away all queued packets and buffered data
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
avcodec_flush_buffers(_ffmpeg->format_ctx->streams[_ffmpeg->video_streams[i]]->codec);
for (size_t j = 0; j < _ffmpeg->video_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->video_packet_queues[i][j]);
}
_ffmpeg->video_packet_queues[i].clear();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
avcodec_flush_buffers(_ffmpeg->format_ctx->streams[_ffmpeg->audio_streams[i]]->codec);
_ffmpeg->audio_buffers[i].clear();
for (size_t j = 0; j < _ffmpeg->audio_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->audio_packet_queues[i][j]);
}
_ffmpeg->audio_packet_queues[i].clear();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
if (_ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[i]]->codec->codec_id != CODEC_ID_TEXT)
{
// CODEC_ID_TEXT has no decoder, so we cannot flush its buffers
avcodec_flush_buffers(_ffmpeg->format_ctx->streams[_ffmpeg->subtitle_streams[i]]->codec);
}
_ffmpeg->subtitle_box_buffers[i].clear();
for (size_t j = 0; j < _ffmpeg->subtitle_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->subtitle_packet_queues[i][j]);
}
_ffmpeg->subtitle_packet_queues[i].clear();
}
// The next read request must update the position
for (size_t i = 0; i < _ffmpeg->video_streams.size(); i++)
{
_ffmpeg->video_last_timestamps[i] = std::numeric_limits::min();
}
for (size_t i = 0; i < _ffmpeg->audio_streams.size(); i++)
{
_ffmpeg->audio_last_timestamps[i] = std::numeric_limits::min();
}
for (size_t i = 0; i < _ffmpeg->subtitle_streams.size(); i++)
{
_ffmpeg->subtitle_last_timestamps[i] = std::numeric_limits::min();
}
_ffmpeg->pos = std::numeric_limits::min();
// Seek
int e = av_seek_frame(_ffmpeg->format_ctx, -1,
dest_pos * AV_TIME_BASE / 1000000,
dest_pos < _ffmpeg->pos ? AVSEEK_FLAG_BACKWARD : 0);
if (e < 0)
{
msg::err(_("%s: Seeking failed."), _url.c_str());
}
// Restart packet reading
_ffmpeg->reader->reset();
_ffmpeg->reader->start();
}
void media_object::close()
{
if (_ffmpeg)
{
try
{
// Stop decoder threads
for (size_t i = 0; i < _ffmpeg->video_decode_threads.size(); i++)
{
_ffmpeg->video_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->audio_decode_threads.size(); i++)
{
_ffmpeg->audio_decode_threads[i].finish();
}
for (size_t i = 0; i < _ffmpeg->subtitle_decode_threads.size(); i++)
{
_ffmpeg->subtitle_decode_threads[i].finish();
}
// Stop reading packets
_ffmpeg->reader->finish();
}
catch (...)
{
}
if (_ffmpeg->format_ctx)
{
for (size_t i = 0; i < _ffmpeg->video_frames.size(); i++)
{
av_free(_ffmpeg->video_frames[i]);
}
for (size_t i = 0; i < _ffmpeg->video_buffered_frames.size(); i++)
{
av_free(_ffmpeg->video_buffered_frames[i]);
}
for (size_t i = 0; i < _ffmpeg->video_buffers.size(); i++)
{
av_free(_ffmpeg->video_buffers[i]);
}
for (size_t i = 0; i < _ffmpeg->video_sws_frames.size(); i++)
{
av_free(_ffmpeg->video_sws_frames[i]);
}
for (size_t i = 0; i < _ffmpeg->video_sws_buffers.size(); i++)
{
av_free(_ffmpeg->video_sws_buffers[i]);
}
for (size_t i = 0; i < _ffmpeg->video_codec_ctxs.size(); i++)
{
if (i < _ffmpeg->video_codecs.size() && _ffmpeg->video_codecs[i])
{
avcodec_close(_ffmpeg->video_codec_ctxs[i]);
}
}
for (size_t i = 0; i < _ffmpeg->video_sws_ctxs.size(); i++)
{
sws_freeContext(_ffmpeg->video_sws_ctxs[i]);
}
for (size_t i = 0; i < _ffmpeg->video_packet_queues.size(); i++)
{
if (_ffmpeg->video_packet_queues[i].size() > 0)
{
msg::dbg(_url + ": " + str::from(_ffmpeg->video_packet_queues[i].size())
+ " unprocessed packets in video stream " + str::from(i));
}
for (size_t j = 0; j < _ffmpeg->video_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->video_packet_queues[i][j]);
}
}
for (size_t i = 0; i < _ffmpeg->video_packets.size(); i++)
{
av_free_packet(&(_ffmpeg->video_packets[i]));
}
for (size_t i = 0; i < _ffmpeg->audio_codec_ctxs.size(); i++)
{
if (i < _ffmpeg->audio_codecs.size() && _ffmpeg->audio_codecs[i])
{
avcodec_close(_ffmpeg->audio_codec_ctxs[i]);
}
}
for (size_t i = 0; i < _ffmpeg->audio_packet_queues.size(); i++)
{
if (_ffmpeg->audio_packet_queues[i].size() > 0)
{
msg::dbg(_url + ": " + str::from(_ffmpeg->audio_packet_queues[i].size())
+ " unprocessed packets in audio stream " + str::from(i));
}
for (size_t j = 0; j < _ffmpeg->audio_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->audio_packet_queues[i][j]);
}
}
for (size_t i = 0; i < _ffmpeg->audio_tmpbufs.size(); i++)
{
av_free(_ffmpeg->audio_tmpbufs[i]);
}
for (size_t i = 0; i < _ffmpeg->subtitle_codec_ctxs.size(); i++)
{
if (i < _ffmpeg->subtitle_codecs.size() && _ffmpeg->subtitle_codecs[i])
{
avcodec_close(_ffmpeg->subtitle_codec_ctxs[i]);
}
}
for (size_t i = 0; i < _ffmpeg->subtitle_packet_queues.size(); i++)
{
if (_ffmpeg->subtitle_packet_queues[i].size() > 0)
{
msg::dbg(_url + ": " + str::from(_ffmpeg->subtitle_packet_queues[i].size())
+ " unprocessed packets in subtitle stream " + str::from(i));
}
for (size_t j = 0; j < _ffmpeg->subtitle_packet_queues[i].size(); j++)
{
av_free_packet(&_ffmpeg->subtitle_packet_queues[i][j]);
}
}
avformat_close_input(&(_ffmpeg->format_ctx));
}
delete _ffmpeg->reader;
delete _ffmpeg;
_ffmpeg = NULL;
}
_url = "";
_is_device = false;
_tag_names.clear();
_tag_values.clear();
}