#include <iomanip>
#include <iostream>
#include <stdint.h>
+#include <boost/lexical_cast.hpp>
extern "C" {
#include <tiffio.h>
#include <libavcodec/avcodec.h>
#include "util.h"
#include "log.h"
#include "ffmpeg_decoder.h"
+#include "subtitle.h"
using namespace std;
using namespace boost;
, _format_context (0)
, _video_stream (-1)
, _audio_stream (-1)
+ , _subtitle_stream (-1)
, _frame (0)
, _video_codec_context (0)
, _video_codec (0)
, _audio_codec_context (0)
, _audio_codec (0)
+ , _subtitle_codec_context (0)
+ , _subtitle_codec (0)
{
setup_general ();
setup_video ();
setup_audio ();
+ setup_subtitle ();
}
FFmpegDecoder::~FFmpegDecoder ()
if (_video_codec_context) {
avcodec_close (_video_codec_context);
}
+
+ if (_subtitle_codec_context) {
+ avcodec_close (_subtitle_codec_context);
+ }
av_free (_frame);
avformat_close_input (&_format_context);
if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
_video_stream = i;
} else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
- _audio_stream = i;
+ if (_audio_stream == -1) {
+ _audio_stream = i;
+ }
+ _audio_streams.push_back (Stream (stream_name (_format_context->streams[i]), i));
+ } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
+ if (_subtitle_stream == -1) {
+ _subtitle_stream = i;
+ }
+ _subtitle_streams.push_back (Stream (stream_name (_format_context->streams[i]), i));
}
}
if (_video_stream < 0) {
throw DecodeError ("could not find video stream");
}
- if (_audio_stream < 0) {
- throw DecodeError ("could not find audio stream");
- }
_frame = avcodec_alloc_frame ();
if (_frame == 0) {
void
FFmpegDecoder::setup_audio ()
{
+ if (_audio_stream < 0) {
+ return;
+ }
+
_audio_codec_context = _format_context->streams[_audio_stream]->codec;
_audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
if (_audio_codec == 0) {
throw DecodeError ("could not find audio decoder");
}
-
+
if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
throw DecodeError ("could not open audio decoder");
}
+
+ /* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
+ so bodge it here. No idea why we should have to do this.
+ */
+
+ if (_audio_codec_context->channel_layout == 0) {
+ _audio_codec_context->channel_layout = av_get_default_channel_layout (audio_channels ());
+ }
+}
+
+void
+FFmpegDecoder::setup_subtitle ()
+{
+ if (_subtitle_stream < 0) {
+ return;
+ }
+
+ _subtitle_codec_context = _format_context->streams[_subtitle_stream]->codec;
+ _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
+
+ if (_subtitle_codec == 0) {
+ throw DecodeError ("could not find subtitle decoder");
+ }
+
+ if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
+ throw DecodeError ("could not open subtitle decoder");
+ }
}
+
bool
FFmpegDecoder::do_pass ()
{
int r = av_read_frame (_format_context, &_packet);
if (r < 0) {
+ if (r != AVERROR_EOF) {
+ throw DecodeError ("error on av_read_frame");
+ }
+
+ /* Get any remaining frames */
+
+ _packet.data = 0;
+ _packet.size = 0;
+
+ int frame_finished;
+
+ while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ process_video (_frame);
+ }
+
+ if (_audio_stream >= 0 && _opt->decode_audio) {
+ while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+ int const data_size = av_samples_get_buffer_size (
+ 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
+ );
+
+ assert (_audio_codec_context->channels == _fs->audio_channels());
+ process_audio (_frame->data[0], data_size);
+ }
+ }
+
return true;
}
- if (_packet.stream_index == _video_stream && _opt->decode_video) {
-
+ if (_packet.stream_index == _video_stream) {
+
int frame_finished;
if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
process_video (_frame);
}
- } else if (_packet.stream_index == _audio_stream && _opt->decode_audio) {
+ } else if (_audio_stream >= 0 && _packet.stream_index == _audio_stream && _opt->decode_audio) {
avcodec_get_frame_defaults (_frame);
0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
);
- assert (_audio_codec_context->channels == _fs->audio_channels);
+ assert (_audio_codec_context->channels == _fs->audio_channels());
process_audio (_frame->data[0], data_size);
}
+
+ } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream && _opt->decode_subtitles) {
+
+ int got_subtitle;
+ AVSubtitle sub;
+ if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) && got_subtitle) {
+ process_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub)));
+ avsubtitle_free (&sub);
+ }
}
av_free_packet (&_packet);
float
FFmpegDecoder::frames_per_second () const
{
- return av_q2d (_format_context->streams[_video_stream]->avg_frame_rate);
+ AVStream* s = _format_context->streams[_video_stream];
+
+ if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
+ return av_q2d (s->avg_frame_rate);
+ }
+
+ return av_q2d (s->r_frame_rate);
}
int
if (_audio_codec_context == 0) {
return 0;
}
-
+
return _audio_codec_context->channels;
}
AVSampleFormat
FFmpegDecoder::audio_sample_format () const
{
+ if (_audio_codec_context == 0) {
+ return (AVSampleFormat) 0;
+ }
+
return _audio_codec_context->sample_fmt;
}
int64_t
FFmpegDecoder::audio_channel_layout () const
{
+ if (_audio_codec_context == 0) {
+ return 0;
+ }
+
return _audio_codec_context->channel_layout;
}
return _video_codec_context->sample_aspect_ratio.den;
}
+bool
+FFmpegDecoder::has_subtitles () const
+{
+ return (_subtitle_stream != -1);
+}
+
+vector<Stream>
+FFmpegDecoder::audio_streams () const
+{
+ return _audio_streams;
+}
+
+vector<Stream>
+FFmpegDecoder::subtitle_streams () const
+{
+ return _subtitle_streams;
+}
+
+void
+FFmpegDecoder::set_audio_stream (int s)
+{
+ _audio_stream = s;
+ setup_audio ();
+}
+
+void
+FFmpegDecoder::set_subtitle_stream (int s)
+{
+ _subtitle_stream = s;
+ setup_subtitle ();
+}
+
+string
+FFmpegDecoder::stream_name (AVStream* s) const
+{
+ stringstream n;
+
+ AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0);
+ if (lang) {
+ n << lang->value;
+ }
+
+ AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0);
+ if (title) {
+ if (!n.str().empty()) {
+ n << " ";
+ }
+ n << title->value;
+ }
+
+ if (n.str().empty()) {
+ n << "unknown";
+ }
+
+ return n.str ();
+}