/* * Copyright 2008-2013 Various Authors * Copyright 2007 Kevin Ko * * 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 2 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 . */ #include "../ip.h" #include "../xmalloc.h" #include "../debug.h" #include "../utils.h" #include "../comment.h" #ifdef HAVE_CONFIG #include "../config/ffmpeg.h" #endif #include #include #include #include #include #include #ifndef AVUTIL_MATHEMATICS_H #include #endif struct ffmpeg_private { AVCodecContext *codec_ctx; AVFormatContext *format_ctx; AVCodec const *codec; SwrContext *swr; int stream_index; AVPacket *pkt; AVFrame *frame; double seek_ts; int64_t skip_samples; /* A buffer to hold swr_convert()-ed samples */ AVFrame *swr_frame; int swr_frame_samples_cap; int swr_frame_start; /* Bitrate estimation */ unsigned long curr_size; unsigned long curr_duration; }; static const char *ffmpeg_errmsg(int err) { static char errstr[AV_ERROR_MAX_STRING_SIZE]; av_strerror(err, errstr, AV_ERROR_MAX_STRING_SIZE); return errstr; } static void ffmpeg_init(void) { static int inited = 0; if (inited != 0) return; inited = 1; av_log_set_level(AV_LOG_QUIET); #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) /* We could register decoders explicitly to save memory, but we have to * be careful about compatibility. */ av_register_all(); #endif } static int ffmpeg_open_input(struct input_plugin_data *ip_data, struct ffmpeg_private *priv) { AVFormatContext *ic = NULL; AVCodecContext *cc = NULL; AVCodecParameters *cp = NULL; AVCodec const *codec = NULL; int stream_index = -1; int err; int res = avformat_open_input(&ic, ip_data->filename, NULL, NULL); if (res < 0) { err = -IP_ERROR_FILE_FORMAT; goto err; } res = avformat_find_stream_info(ic, NULL); if (res < 0) { d_print("unable to find stream info\n"); err = -IP_ERROR_FILE_FORMAT; goto err; } for (int i = 0; i < ic->nb_streams; i++) { cp = ic->streams[i]->codecpar; if (cp->codec_type == AVMEDIA_TYPE_AUDIO) { stream_index = i; break; } } if (stream_index == -1) { d_print("could not find audio stream\n"); err = -IP_ERROR_FILE_FORMAT; goto err_silent; } codec = avcodec_find_decoder(cp->codec_id); if (!codec) { d_print("codec (id: %d, name: %s) not found\n", cc->codec_id, avcodec_get_name(cc->codec_id)); err = -IP_ERROR_UNSUPPORTED_FILE_TYPE; goto err_silent; } cc = avcodec_alloc_context3(codec); avcodec_parameters_to_context(cc, cp); res = avcodec_open2(cc, codec, NULL); if (res < 0) { d_print("could not open codec (id: %d, name: %s)\n", cc->codec_id, avcodec_get_name(cc->codec_id)); err = -IP_ERROR_UNSUPPORTED_FILE_TYPE; goto err; } priv->format_ctx = ic; priv->codec_ctx = cc; priv->codec = codec; priv->stream_index = stream_index; return 0; err: d_print("%s\n", ffmpeg_errmsg(res)); err_silent: avcodec_free_context(&cc); avformat_close_input(&ic); return err; } static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc, sample_format_t *sf_out, enum AVSampleFormat *out_sample_fmt) { int out_sample_rate = min_u(cc->sample_rate, 384000); sample_format_t sf = sf_rate(out_sample_rate) | sf_host_endian(); av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0); av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0); switch (cc->sample_fmt) { case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP: case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P: sf |= sf_bits(32) | sf_signed(1); *out_sample_fmt = AV_SAMPLE_FMT_S32; break; default: sf |= sf_bits(16) | sf_signed(1); *out_sample_fmt = AV_SAMPLE_FMT_S16; } av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0); av_opt_set_sample_fmt(swr, "out_sample_fmt", *out_sample_fmt, 0); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) sf |= sf_channels(cc->ch_layout.nb_channels); if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels); av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0); av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0); #else sf |= sf_channels(cc->channels); av_opt_set_int(swr, "in_channel_layout", av_get_default_channel_layout(cc->channels), 0); av_opt_set_int(swr, "out_channel_layout", av_get_default_channel_layout(cc->channels), 0); #endif *sf_out = sf; } static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv, sample_format_t sf, enum AVSampleFormat out_sample_fmt) { AVCodecContext *cc = priv->codec_ctx; AVFrame *frame = av_frame_alloc(); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) av_channel_layout_copy(&frame->ch_layout, &cc->ch_layout); #else frame->channel_layout = av_get_default_channel_layout(cc->channels); #endif frame->sample_rate = sf_get_rate(sf); frame->format = out_sample_fmt; /* NOTE: 10 sec is probably too much, but the amount of space * needed for swr_convert() is unpredictable */ frame->nb_samples = 10 * sf_get_rate(sf); int res = av_frame_get_buffer(frame, 0); if (res < 0) { d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res)); return -IP_ERROR_INTERNAL; } priv->swr_frame_samples_cap = frame->nb_samples; frame->nb_samples = 0; priv->swr_frame = frame; return 0; } static void ffmpeg_free(struct ffmpeg_private *priv) { avcodec_free_context(&priv->codec_ctx); avformat_close_input(&priv->format_ctx); swr_free(&priv->swr); av_frame_free(&priv->frame); av_packet_free(&priv->pkt); av_frame_free(&priv->swr_frame); } static int ffmpeg_open(struct input_plugin_data *ip_data) { struct ffmpeg_private priv; enum AVSampleFormat out_sample_fmt; memset(&priv, 0, sizeof(struct ffmpeg_private)); ffmpeg_init(); int err = ffmpeg_open_input(ip_data, &priv); if (err < 0) return err; priv.pkt = av_packet_alloc(); priv.frame = av_frame_alloc(); priv.seek_ts = -1; priv.swr = swr_alloc(); ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx, &ip_data->sf, &out_sample_fmt); swr_init(priv.swr); err = ffmpeg_init_swr_frame(&priv, ip_data->sf, out_sample_fmt); if (err < 0) { ffmpeg_free(&priv); return err; } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) channel_map_init_waveex(priv.codec_ctx->ch_layout.nb_channels, priv.codec_ctx->ch_layout.u.mask, ip_data->channel_map); #else channel_map_init_waveex(priv.codec_ctx->channels, priv.codec_ctx->channel_layout, ip_data->channel_map); #endif ip_data->private = xnew(struct ffmpeg_private, 1); memcpy(ip_data->private, &priv, sizeof(struct ffmpeg_private)); return 0; } static int ffmpeg_close(struct input_plugin_data *ip_data) { ffmpeg_free(ip_data->private); free(ip_data->private); ip_data->private = NULL; return 0; } static int64_t ffmpeg_calc_skip_samples(struct ffmpeg_private *priv) { int64_t ts; if (priv->frame->pts >= 0) { ts = priv->frame->pts; } else if (priv->frame->pkt_dts >= 0) { ts = priv->frame->pkt_dts; } else { d_print("AVFrame.pts and AVFrame.pkt_dts are unset\n"); return -1; } AVStream *s = priv->format_ctx->streams[priv->stream_index]; double frame_ts = ts * av_q2d(s->time_base); d_print("seek_ts: %.6fs, frame_ts: %.6fs\n", priv->seek_ts, frame_ts); if (frame_ts >= priv->seek_ts) return 0; return (priv->seek_ts - frame_ts) * priv->frame->sample_rate; } static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv) { if (priv->skip_samples >= priv->frame->nb_samples) { d_print("skipping frame: %d samples\n", priv->frame->nb_samples); priv->skip_samples -= priv->frame->nb_samples; priv->frame->nb_samples = 0; return; } int bps = av_get_bytes_per_sample(priv->frame->format); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) int channels = priv->codec_ctx->ch_layout.nb_channels; #else int channels = priv->codec_ctx->channels; #endif priv->frame->nb_samples -= priv->skip_samples; /* Just modify frame's data pointer because it's throw-away */ if (av_sample_fmt_is_planar(priv->frame->format)) { for (int i = 0; i < channels; i++) priv->frame->extended_data[i] += priv->skip_samples * bps; } else { priv->frame->extended_data[0] += priv->skip_samples * channels * bps; } d_print("skipping %lld samples\n", (long long)priv->skip_samples); priv->skip_samples = 0; } /* * return: * <0 - error * 0 - eof * >0 - ok */ static int ffmpeg_get_frame(struct ffmpeg_private *priv) { int res; retry: res = avcodec_receive_frame(priv->codec_ctx, priv->frame); if (res == AVERROR(EAGAIN)) { av_packet_unref(priv->pkt); res = av_read_frame(priv->format_ctx, priv->pkt); if (res < 0) goto err; if (priv->pkt->stream_index != priv->stream_index) goto retry; priv->curr_size += priv->pkt->size; priv->curr_duration += priv->pkt->duration; res = avcodec_send_packet(priv->codec_ctx, priv->pkt); if (res == 0 || res == AVERROR(EAGAIN)) goto retry; } if (res < 0) goto err; if (priv->seek_ts > 0) { priv->skip_samples = ffmpeg_calc_skip_samples(priv); if (priv->skip_samples >= 0) priv->seek_ts = -1; } if (priv->skip_samples > 0) { ffmpeg_skip_frame_part(priv); if (priv->frame->nb_samples == 0) goto retry; } return 1; err: if (res == AVERROR_EOF) return 0; d_print("%s\n", ffmpeg_errmsg(res)); return -IP_ERROR_INTERNAL; } static int ffmpeg_convert_frame(struct ffmpeg_private *priv) { int res = swr_convert(priv->swr, priv->swr_frame->extended_data, priv->swr_frame_samples_cap, (const uint8_t **)priv->frame->extended_data, priv->frame->nb_samples); if (res >= 0) { priv->swr_frame->nb_samples = res; priv->swr_frame_start = 0; return res; } d_print("%s\n", ffmpeg_errmsg(res)); return -IP_ERROR_INTERNAL; } static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count) { struct ffmpeg_private *priv = ip_data->private; int written = 0; int res; count /= sf_get_frame_size(ip_data->sf); while (count) { if (priv->swr_frame->nb_samples == 0) { res = ffmpeg_get_frame(priv); if (res == 0) break; else if (res < 0) return res; res = ffmpeg_convert_frame(priv); if (res < 0) return res; } int copy_frames = min_i(count, priv->swr_frame->nb_samples); int copy_bytes = copy_frames * sf_get_frame_size(ip_data->sf); void *dst = priv->swr_frame->extended_data[0] + priv->swr_frame_start; memcpy(buffer + written, dst, copy_bytes); priv->swr_frame->nb_samples -= copy_frames; priv->swr_frame_start += copy_bytes; count -= copy_frames; written += copy_bytes; } return written; } static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset) { struct ffmpeg_private *priv = ip_data->private; AVStream *st = priv->format_ctx->streams[priv->stream_index]; priv->seek_ts = offset; priv->skip_samples = 0; int64_t ts = offset / av_q2d(st->time_base); int ret = avformat_seek_file(priv->format_ctx, priv->stream_index, 0, ts, ts, 0); if (ret < 0) return -IP_ERROR_FUNCTION_NOT_SUPPORTED; priv->swr_frame->nb_samples = 0; priv->swr_frame_start = 0; avcodec_flush_buffers(priv->codec_ctx); swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */ return 0; } static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metadata) { AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { if (tag->value[0]) comments_add_const(c, tag->key, tag->value); } } static int ffmpeg_read_comments(struct input_plugin_data *ip_data, struct keyval **comments) { struct ffmpeg_private *priv = ip_data->private; AVFormatContext *ic = priv->format_ctx; GROWING_KEYVALS(c); ffmpeg_read_metadata(&c, ic->metadata); for (unsigned i = 0; i < ic->nb_streams; i++) { ffmpeg_read_metadata(&c, ic->streams[i]->metadata); } keyvals_terminate(&c); *comments = c.keyvals; return 0; } static int ffmpeg_duration(struct input_plugin_data *ip_data) { struct ffmpeg_private *priv = ip_data->private; return priv->format_ctx->duration / AV_TIME_BASE; } static long ffmpeg_bitrate(struct input_plugin_data *ip_data) { struct ffmpeg_private *priv = ip_data->private; long bitrate = priv->format_ctx->bit_rate; return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED; } static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data) { struct ffmpeg_private *priv = ip_data->private; AVStream *st = priv->format_ctx->streams[priv->stream_index]; long bitrate = -1; /* ape codec returns silly numbers */ if (priv->codec->id == AV_CODEC_ID_APE) return -1; if (priv->curr_duration > 0) { double seconds = priv->curr_duration * av_q2d(st->time_base); bitrate = (8 * priv->curr_size) / seconds; priv->curr_size = 0; priv->curr_duration = 0; } return bitrate; } static char *ffmpeg_codec(struct input_plugin_data *ip_data) { struct ffmpeg_private *priv = ip_data->private; return xstrdup(priv->codec->name); } static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data) { struct ffmpeg_private *priv = ip_data->private; const char *profile; profile = av_get_profile_name(priv->codec, priv->codec_ctx->profile); return profile ? xstrdup(profile) : NULL; } const struct input_plugin_ops ip_ops = { .open = ffmpeg_open, .close = ffmpeg_close, .read = ffmpeg_read, .seek = ffmpeg_seek, .read_comments = ffmpeg_read_comments, .duration = ffmpeg_duration, .bitrate = ffmpeg_bitrate, .bitrate_current = ffmpeg_current_bitrate, .codec = ffmpeg_codec, .codec_profile = ffmpeg_codec_profile }; const int ip_priority = 30; const char *const ip_extensions[] = { "aa", "aac", "ac3", "aif", "aifc", "aiff", "ape", "au", "dsf", "fla", "flac", "m4a", "m4b", "mka", "mkv", "mp+", "mp2", "mp3", "mp4", "mpc", "mpp", "ogg", "opus", "shn", "tak", "tta", "wav", "webm", "wma", "wv", #ifdef USE_FALLBACK_IP "*", #endif NULL }; const char *const ip_mime_types[] = { NULL }; const struct input_plugin_opt ip_options[] = { { NULL } }; const unsigned ip_abi_version = IP_ABI_VERSION;