Beispiel #1
0
video_frame media_input::finish_video_frame_read()
{
    assert(_active_video_stream >= 0);
    if (!_have_active_video_read)
    {
        start_video_frame_read();
    }
    video_frame frame;
    if (_video_frame.stereo_layout == parameters::layout_separate)
    {
        int o0, s0, o1, s1;
        get_video_stream(0, o0, s0);
        get_video_stream(1, o1, s1);
        video_frame f0 = _media_objects[o0].finish_video_frame_read(s0);
        video_frame f1 = _media_objects[o1].finish_video_frame_read(s1);
        if (f0.is_valid() && f1.is_valid())
        {
            frame = _video_frame;
            for (int p = 0; p < 3; p++)
            {
                frame.data[0][p] = f0.data[0][p];
                frame.data[1][p] = f1.data[0][p];
                frame.line_size[0][p] = f0.line_size[0][p];
                frame.line_size[1][p] = f1.line_size[0][p];
            }
            frame.presentation_time = f0.presentation_time;
        }
    }
    else
    {
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        video_frame f = _media_objects[o].finish_video_frame_read(s);
        if (f.is_valid())
        {
            frame = _video_frame;
            for (int v = 0; v < 2; v++)
            {
                for (int p = 0; p < 3; p++)
                {
                    frame.data[v][p] = f.data[v][p];
                    frame.line_size[v][p] = f.line_size[v][p];
                }
            }
            frame.presentation_time = f.presentation_time;
        }
    }
    _have_active_video_read = false;
    return frame;
}
Beispiel #2
0
void media_input::select_video_stream(int video_stream)
{
    if (_have_active_video_read)
    {
        (void)finish_video_frame_read();
    }
    if (_have_active_audio_read)
    {
        (void)finish_audio_blob_read();
    }
    if (_have_active_subtitle_read)
    {
        (void)finish_subtitle_box_read();
    }
    assert(video_stream >= 0);
    assert(video_stream < video_streams());
    if (_video_frame.stereo_layout == parameters::layout_separate)
    {
        _active_video_stream = 0;
        for (size_t i = 0; i < _media_objects.size(); i++)
        {
            for (int j = 0; j < _media_objects[i].video_streams(); j++)
            {
                _media_objects[i].video_stream_set_active(j, true);
            }
        }
    }
    else
    {
        _active_video_stream = video_stream;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        for (size_t i = 0; i < _media_objects.size(); i++)
        {
            for (int j = 0; j < _media_objects[i].video_streams(); j++)
            {
                _media_objects[i].video_stream_set_active(j, (i == static_cast<size_t>(o) && j == s));
            }
        }
    }
    // Re-set video frame template
    parameters::stereo_layout_t stereo_layout_bak = _video_frame.stereo_layout;
    bool stereo_layout_swap_bak = _video_frame.stereo_layout_swap;
    int o, s;
    get_video_stream(_active_video_stream, o, s);
    _video_frame = _media_objects[o].video_frame_template(s);
    _video_frame.stereo_layout = stereo_layout_bak;
    _video_frame.stereo_layout_swap = stereo_layout_swap_bak;
    _video_frame.set_view_dimensions();
}
Beispiel #3
0
int media_input::video_frame_rate_denominator() const
{
    assert(_active_video_stream >= 0);
    int o, s;
    get_video_stream(_active_video_stream, o, s);
    return _media_objects[o].video_frame_rate_denominator(s);
}
Beispiel #4
0
/*
 * picture_to_frame reads and decodes
 * the picture file
 *
 * side effects: allocates an AVFrame which
 * must be freed with av_free_frame
 */
FFMPEG_tmp * picture_to_frame(char *filepath) {

    AVFormatContext *fctx = NULL;
    int              stream_no;
    AVCodecContext  *cctx = NULL;
    AVCodec         *c = NULL;
    AVFrame         *frame = NULL;

    FFMPEG_tmp      *tmp = malloc(sizeof(FFMPEG_tmp));

    fctx = get_fcontext(filepath);
    if (fctx == NULL) {
        fprintf(stderr, "Fatal: could not open %s\n", filepath);
        avformat_close_input(&fctx);
        exit(1);
    }

    stream_no = get_video_stream(fctx);
    if (stream_no == -1) {
        fprintf(stderr, "Fatal: could not find video stream\n");
        avformat_close_input(&fctx);
        exit(1);
    }

    cctx = get_ccontext(fctx, stream_no);
    if (cctx == NULL) {
        fprintf(stderr, "Fatal: no codec context initialized\n");
        avcodec_close(cctx);
        avformat_close_input(&fctx);
        exit(1);
    }

    c = get_codec(cctx);
    if (c == NULL) {
        fprintf(stderr, "Fatal: could not open codec\n");
        avcodec_close(cctx);
        avformat_close_input(&fctx);
        exit(1);
    }

    frame = decode_picture(fctx, stream_no, cctx);
    if (frame == NULL) {
        avcodec_close(cctx);
        avformat_close_input(&fctx);
        fprintf(stderr, "Fatal: could not decode image\n");
        exit(1);
    }

    /* clean up successful run */
    tmp->frame = frame;
    tmp->fctx = fctx;
    tmp->cctx = cctx;
    tmp->c = c;

    return tmp;
}
Beispiel #5
0
void media_input::start_video_frame_read()
{
    assert(_active_video_stream >= 0);
    if (_have_active_video_read)
    {
        return;
    }
    if (_video_frame.stereo_layout == video_frame::separate)
    {
        int o0, s0, o1, s1;
        get_video_stream(0, o0, s0);
        get_video_stream(1, o1, s1);
        _media_objects[o0].start_video_frame_read(s0);
        _media_objects[o1].start_video_frame_read(s1);
    }
    else
    {
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        _media_objects[o].start_video_frame_read(s);
    }
    _have_active_video_read = true;
}
Beispiel #6
0
int64_t media_input::tell()
{
    int64_t pos = std::numeric_limits<int64_t>::min();
    int o, s;
    if (_active_audio_stream >= 0)
    {
        get_audio_stream(_active_audio_stream, o, s);
        pos = _media_objects[o].tell();
    }
    else if (_active_video_stream >= 0)
    {
        get_video_stream(_active_video_stream, o, s);
        pos = _media_objects[o].tell();
    }
    return pos;
}
Beispiel #7
0
void media_input::select_video_stream(int video_stream)
{
    if (_have_active_video_read)
    {
        (void)finish_video_frame_read();
    }
    if (_have_active_audio_read)
    {
        (void)finish_audio_blob_read();
    }
    if (_have_active_subtitle_read)
    {
        (void)finish_subtitle_box_read();
    }
    assert(video_stream >= 0);
    assert(video_stream < video_streams());
    if (_video_frame.stereo_layout == video_frame::separate)
    {
        _active_video_stream = 0;
        for (size_t i = 0; i < _media_objects.size(); i++)
        {
            for (int j = 0; j < _media_objects[i].video_streams(); j++)
            {
                _media_objects[i].video_stream_set_active(j, true);
            }
        }
    }
    else
    {
        _active_video_stream = video_stream;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        for (size_t i = 0; i < _media_objects.size(); i++)
        {
            for (int j = 0; j < _media_objects[i].video_streams(); j++)
            {
                _media_objects[i].video_stream_set_active(j, (i == static_cast<size_t>(o) && j == s));
            }
        }
    }
}
Beispiel #8
0
bool media_input::stereo_layout_is_supported(parameters::stereo_layout_t layout, bool) const
{
    if (video_streams() < 1)
    {
        return false;
    }
    assert(_active_video_stream >= 0);
    assert(_active_video_stream < video_streams());
    int o, s;
    get_video_stream(_active_video_stream, o, s);
    const video_frame &t = _media_objects[o].video_frame_template(s);
    bool supported = true;
    if (((layout == parameters::layout_left_right || layout == parameters::layout_left_right_half) && t.raw_width % 2 != 0)
            || ((layout == parameters::layout_top_bottom || layout == parameters::layout_top_bottom_half) && t.raw_height % 2 != 0)
            || (layout == parameters::layout_even_odd_rows && t.raw_height % 2 != 0)
            || (layout == parameters::layout_separate && !_supports_stereo_layout_separate))
    {
        supported = false;
    }
    return supported;
}
Beispiel #9
0
void media_input::set_stereo_layout(parameters::stereo_layout_t layout, bool swap)
{
    assert(stereo_layout_is_supported(layout, swap));
    if (_have_active_video_read)
    {
        (void)finish_video_frame_read();
    }
    if (_have_active_audio_read)
    {
        (void)finish_audio_blob_read();
    }
    if (_have_active_subtitle_read)
    {
        (void)finish_subtitle_box_read();
    }
    int o, s;
    get_video_stream(_active_video_stream, o, s);
    const video_frame &t = _media_objects[o].video_frame_template(s);
    _video_frame = t;
    _video_frame.stereo_layout = layout;
    _video_frame.stereo_layout_swap = swap;
    _video_frame.set_view_dimensions();
    // Reset active stream in case we switched to or from 'separate'.
    select_video_stream(_active_video_stream);
    if (layout == parameters::layout_separate)
    {
        // If we switched the layout to 'separate', then we have to seek to the
        // position of the first video stream, or else the second video stream
        // is out of sync.
        int64_t pos = _media_objects[o].tell();
        if (pos > std::numeric_limits<int64_t>::min())
        {
            seek(pos);
        }
    }
}
Beispiel #10
0
void media_input::open(const std::vector<std::string> &urls, const device_request &dev_request)
{
    assert(urls.size() > 0);

    // Open media objects
    _is_device = dev_request.is_device();
    _media_objects.resize(urls.size());
    for (size_t i = 0; i < urls.size(); i++)
    {
        _media_objects[i].open(urls[i], dev_request);
    }

    // Construct id for this input
    _id = basename(_media_objects[0].url());
    for (size_t i = 1; i < _media_objects.size(); i++)
    {
        _id += '/';
        _id += basename(_media_objects[i].url());
    }

    // Gather metadata
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        // Note that we may have multiple identical tag names in our metadata
        for (size_t j = 0; j < _media_objects[i].tags(); j++)
        {
            _tag_names.push_back(_media_objects[i].tag_name(j));
            _tag_values.push_back(_media_objects[i].tag_value(j));
        }
    }

    // Gather streams and stream names
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        for (int j = 0; j < _media_objects[i].video_streams(); j++)
        {
            _video_stream_names.push_back(_media_objects[i].video_frame_template(j).format_info());
        }
    }
    if (_video_stream_names.size() > 1)
    {
        for (size_t i = 0; i < _video_stream_names.size(); i++)
        {
            _video_stream_names[i].insert(0,
                    std::string(1, '#') + str::from(i + 1) + '/'
                    + str::from(_video_stream_names.size()) + ": ");
        }
    }
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        for (int j = 0; j < _media_objects[i].audio_streams(); j++)
        {
            _audio_stream_names.push_back(_media_objects[i].audio_blob_template(j).format_info());
        }
    }
    if (_audio_stream_names.size() > 1)
    {
        for (size_t i = 0; i < _audio_stream_names.size(); i++)
        {
            _audio_stream_names[i].insert(0,
                    std::string(1, '#') + str::from(i + 1) + '/'
                    + str::from(_audio_stream_names.size()) + ": ");
        }
    }
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        for (int j = 0; j < _media_objects[i].subtitle_streams(); j++)
        {
            _subtitle_stream_names.push_back(_media_objects[i].subtitle_box_template(j).format_info());
        }
    }
    if (_subtitle_stream_names.size() > 1)
    {
        for (size_t i = 0; i < _subtitle_stream_names.size(); i++)
        {
            _subtitle_stream_names[i].insert(0,
                    std::string(1, '#') + str::from(i + 1) + '/'
                    + str::from(_subtitle_stream_names.size()) + ": ");
        }
    }

    // Set duration information
    _duration = std::numeric_limits<int64_t>::max();
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        for (int j = 0; j < _media_objects[i].video_streams(); j++)
        {
            int64_t d = _media_objects[i].video_duration(j);
            if (d < _duration)
            {
                _duration = d;
            }
        }
        for (int j = 0; j < _media_objects[i].audio_streams(); j++)
        {
            int64_t d = _media_objects[i].audio_duration(j);
            if (d < _duration)
            {
                _duration = d;
            }
        }
        // Ignore subtitle stream duration; it seems unreliable and is not important anyway.
    }

    // Skip advertisement in 3dtv.at movies. Only works for single media objects.
    try { _initial_skip = str::to<int64_t>(tag_value("StereoscopicSkip")); } catch (...) { }

    // Find stereo layout and set active video stream(s)
    _supports_stereo_layout_separate = false;
    if (video_streams() == 2)
    {
        int o0, o1, v0, v1;
        get_video_stream(0, o0, v0);
        get_video_stream(1, o1, v1);
        video_frame t0 = _media_objects[o0].video_frame_template(v0);
        video_frame t1 = _media_objects[o1].video_frame_template(v1);
        if (t0.width == t1.width
                && t0.height == t1.height
                && (t0.aspect_ratio <= t1.aspect_ratio && t0.aspect_ratio >= t1.aspect_ratio)
                && t0.layout == t1.layout
                && t0.color_space == t1.color_space
                && t0.value_range == t1.value_range
                && t0.chroma_location == t1.chroma_location)
        {
            _supports_stereo_layout_separate = true;
        }
    }
    if (_supports_stereo_layout_separate)
    {
        _active_video_stream = 0;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        _video_frame = _media_objects[o].video_frame_template(s);
        _video_frame.stereo_layout = parameters::layout_separate;
    }
    else if (video_streams() > 0)
    {
        _active_video_stream = 0;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        _video_frame = _media_objects[o].video_frame_template(s);
    }
    else
    {
        _active_video_stream = -1;
    }
    if (_active_video_stream >= 0)
    {
        select_video_stream(_active_video_stream);
    }

    // Set active audio stream
    _active_audio_stream = (audio_streams() > 0 ? 0 : -1);
    if (_active_audio_stream >= 0)
    {
        int o, s;
        get_audio_stream(_active_audio_stream, o, s);
        _audio_blob = _media_objects[o].audio_blob_template(s);
        select_audio_stream(_active_audio_stream);
    }

    // Set active subtitle stream
    _active_subtitle_stream = -1;       // no subtitles by default

    // Print summary
    msg::inf(_("Input:"));
    for (int i = 0; i < video_streams(); i++)
    {
        int o, s;
        get_video_stream(i, o, s);
        msg::inf(4, _("Video %s: %s"), video_stream_name(i).c_str(),
                _media_objects[o].video_frame_template(s).format_name().c_str());
    }
    if (video_streams() == 0)
    {
        msg::inf(4, _("No video."));
    }
    for (int i = 0; i < audio_streams(); i++)
    {
        int o, s;
        get_audio_stream(i, o, s);
        msg::inf(4, _("Audio %s: %s"), audio_stream_name(i).c_str(),
                _media_objects[o].audio_blob_template(s).format_name().c_str());
    }
    if (audio_streams() == 0)
    {
        msg::inf(4, _("No audio."));
    }
    for (int i = 0; i < subtitle_streams(); i++)
    {
        int o, s;
        get_subtitle_stream(i, o, s);
        msg::inf(4, _("Subtitle %s: %s"), subtitle_stream_name(i).c_str(),
                _media_objects[o].subtitle_box_template(s).format_name().c_str());
    }
    if (subtitle_streams() == 0)
    {
        msg::inf(4, _("No subtitle."));
    }
    msg::inf(4, _("Duration: %g seconds"), duration() / 1e6f);
    if (video_streams() > 0)
    {
        msg::inf(4, _("Stereo layout: %s"), parameters::stereo_layout_to_string(
                    video_frame_template().stereo_layout, video_frame_template().stereo_layout_swap).c_str());
    }
}
Beispiel #11
0
static DecoderContext *init_decoder(const char *filename)
{
    DecoderContext *dc = (DecoderContext *)calloc(1, sizeof(DecoderContext));
    AVCodecContext *codecCtx;

    // Open the stream
    if(avformat_open_input(&(dc->formatCtx), filename, NULL, NULL) != 0) {
        fprintf(stderr, "Couldn't open file");
        exit(1);
    }

    // Retrieve stream information
    if(avformat_find_stream_info(dc->formatCtx, NULL) < 0) {
        fprintf(stderr, "Couldn't find stream information");
        exit(1);
    }

    // Dump information about file onto standard error
    av_dump_format(dc->formatCtx, 0, filename, 0);


    // Get video Stream
    dc->videoStream = get_video_stream(dc->formatCtx);
    if (dc->videoStream == -1) {
        fprintf(stderr, "Couldn't find video stream");
        exit(1);
    }

    codecCtx = dc->formatCtx->streams[dc->videoStream]->codec;

    /* find the decoder */
    dc->codec = avcodec_find_decoder(codecCtx->codec_id);
    if (!dc->codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    /* Allocate codec context */
    dc->codecCtx = avcodec_alloc_context3(dc->codec);
    if (!dc->codecCtx) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    if(avcodec_copy_context(dc->codecCtx, codecCtx) != 0) {
        fprintf(stderr, "Couldn't copy codec context");
        exit(1); // Error copying codec context
    }

    if(dc->codec->capabilities & CODEC_CAP_TRUNCATED)
        dc->codecCtx->flags |= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */

    /* For some codecs, such as msmpeg4 and mpeg4, width and height
       MUST be initialized there because this information is not
       available in the bitstream. */

    /* open it */
    if (avcodec_open2(dc->codecCtx, dc->codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    dc->frame = av_frame_alloc();
    if (!dc->frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    // Allocate input buffer
    dc->numBytes = avpicture_get_size(dc->codecCtx->pix_fmt,
                                      dc->codecCtx->width,
                                      dc->codecCtx->height);

    dc->inbuf = calloc(1, dc->numBytes + FF_INPUT_BUFFER_PADDING_SIZE);

    if (!dc->inbuf) {
        fprintf(stderr, "Could not allocate buffer");
        exit(1);
    }

    memset(dc->inbuf + dc->numBytes, 0, FF_INPUT_BUFFER_PADDING_SIZE);

    dc->frame_count = 0;

    return dc;
}
Beispiel #12
0
void media_input::open(const std::vector<std::string> &urls)
{
    assert(urls.size() > 0);

    // Open media objects
    _media_objects.resize(urls.size());
    for (size_t i = 0; i < urls.size(); i++)
    {
        _media_objects[i].open(urls[i]);
    }

    // Construct id for this input
    _id = basename(_media_objects[0].url());
    for (size_t i = 1; i < _media_objects.size(); i++)
    {
        _id += '/';
        _id += basename(_media_objects[i].url());
    }

    // Gather metadata
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        std::string pfx = (_media_objects.size() == 1 ? "" : str::from(i + 1) + " - ");
        for (size_t j = 0; j < _media_objects[i].tags(); j++)
        {
            _tag_names.push_back(pfx + _media_objects[i].tag_name(j));
            _tag_values.push_back(pfx + _media_objects[i].tag_value(j));
        }
    }

    // Gather streams and stream names
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        std::string pfx = (_media_objects.size() == 1 ? "" : str::from(i + 1) + " - ");
        for (int j = 0; j < _media_objects[i].video_streams(); j++)
        {
            std::string pfx2 = (_media_objects[i].video_streams() == 1 ? "" : str::from(j + 1) + " - ");
            _video_stream_names.push_back(pfx + pfx2
                    + _media_objects[i].video_frame_template(j).format_info());
        }
    }
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        std::string pfx = (_media_objects.size() == 1 ? "" : str::from(i + 1) + " - ");
        for (int j = 0; j < _media_objects[i].audio_streams(); j++)
        {
            std::string pfx2 = (_media_objects[i].audio_streams() == 1 ? "" : str::from(j + 1) + " - ");
            _audio_stream_names.push_back(pfx + pfx2
                    + _media_objects[i].audio_blob_template(j).format_info());
        }
    }
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        std::string pfx = (_media_objects.size() == 1 ? "" : str::from(i + 1) + " - ");
        for (int j = 0; j < _media_objects[i].subtitle_streams(); j++)
        {
            std::string pfx2 = (_media_objects[i].subtitle_streams() == 1 ? "" : str::from(j + 1) + " - ");
            _subtitle_stream_names.push_back(pfx + pfx2
                    + _media_objects[i].subtitle_box_template(j).format_info());
        }
    }

    // Set duration information
    _duration = std::numeric_limits<int64_t>::max();
    for (size_t i = 0; i < _media_objects.size(); i++)
    {
        for (int j = 0; j < _media_objects[i].video_streams(); j++)
        {
            int64_t d = _media_objects[i].video_duration(j);
            if (d < _duration)
            {
                _duration = d;
            }
        }
        for (int j = 0; j < _media_objects[i].audio_streams(); j++)
        {
            int64_t d = _media_objects[i].audio_duration(j);
            if (d < _duration)
            {
                _duration = d;
            }
        }
        for (int j = 0; j < _media_objects[i].subtitle_streams(); j++)
        {
//             int64_t d = _media_objects[i].subtitle_duration(j);
//             if (d < _duration)
//             {
//                 _duration = d;
//             }
        }
    }
 
    // Skip advertisement in 3dtv.at movies. Only works for single media objects.
    try { _initial_skip = str::to<int64_t>(tag_value("StereoscopicSkip")); } catch (...) { }

    // Find stereo layout and set active video stream(s)
    _supports_stereo_layout_separate = false;
    if (video_streams() == 2)
    {
        int o0, o1, v0, v1;
        get_video_stream(0, o0, v0);
        get_video_stream(1, o1, v1);
        video_frame t0 = _media_objects[o0].video_frame_template(v0);
        video_frame t1 = _media_objects[o1].video_frame_template(v1);
        if (t0.width == t1.width
                && t0.height == t1.height
                && (t0.aspect_ratio <= t1.aspect_ratio && t0.aspect_ratio >= t1.aspect_ratio)
                && t0.layout == t1.layout
                && t0.color_space == t1.color_space
                && t0.value_range == t1.value_range
                && t0.chroma_location == t1.chroma_location)
        {
            _supports_stereo_layout_separate = true;
        }
    }
    if (_supports_stereo_layout_separate)
    {
        _active_video_stream = 0;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        _video_frame = _media_objects[o].video_frame_template(s);
        _video_frame.stereo_layout = video_frame::separate;
    }
    else if (video_streams() > 0)
    {
        _active_video_stream = 0;
        int o, s;
        get_video_stream(_active_video_stream, o, s);
        _video_frame = _media_objects[o].video_frame_template(s);
    }
    else
    {
        _active_video_stream = -1;
    }
    if (_active_video_stream >= 0)
    {
        select_video_stream(_active_video_stream);
    }

    // Set active audio stream
    _active_audio_stream = (audio_streams() > 0 ? 0 : -1);
    if (_active_audio_stream >= 0)
    {
        int o, s;
        get_audio_stream(_active_audio_stream, o, s);
        _audio_blob = _media_objects[o].audio_blob_template(s);
        select_audio_stream(_active_audio_stream);
    }

    // Set active subtitle stream
    _active_subtitle_stream = -1;       // no subtitles by default

    // Print summary
    msg::inf("Input:");
    for (int i = 0; i < video_streams(); i++)
    {
        int o, s;
        get_video_stream(i, o, s);
        msg::inf("    Video %s: %s", video_stream_name(i).c_str(),
                _media_objects[o].video_frame_template(s).format_name().c_str());
    }
    if (video_streams() == 0)
    {
        msg::inf("    No video.");
    }
    for (int i = 0; i < audio_streams(); i++)
    {
        int o, s;
        get_audio_stream(i, o, s);
        msg::inf("    Audio %s: %s", audio_stream_name(i).c_str(), 
                _media_objects[o].audio_blob_template(s).format_name().c_str());
    }
    if (audio_streams() == 0)
    {
        msg::inf("    No audio.");
    }
    for (int i = 0; i < subtitle_streams(); i++)
    {
        int o, s;
        get_subtitle_stream(i, o, s);
        msg::inf("    Subtitle %s: %s", subtitle_stream_name(i).c_str(), 
                _media_objects[o].subtitle_box_template(s).format_name().c_str());
    }
    if (subtitle_streams() == 0)
    {
        msg::inf("    No subtitles.");
    }
    msg::inf("    Duration: %g seconds", duration() / 1e6f);
    if (video_streams() > 0)
    {
        msg::inf("    Stereo layout: %s", video_frame::stereo_layout_to_string(
                    video_frame_template().stereo_layout, video_frame_template().stereo_layout_swap).c_str());
    }
}