/** Copyright (C) 2011-2013 Robin Gareus This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser Public License as published by the Free Software Foundation; either version 2.1, 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 Lesser Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "audio_decoder/ad_plugin.h" #ifdef HAVE_FFMPEG #include "ffcompat.h" #ifndef MIN #define MIN(a,b) ( ( (a) < (b) )? (a) : (b) ) #endif typedef struct { AVFormatContext* formatContext; AVCodecContext* codecContext; AVCodec* codec; AVPacket packet; int audioStream; int pkt_len; uint8_t* pkt_ptr; int16_t m_tmpBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE]; int16_t* m_tmpBufferStart; unsigned long m_tmpBufferLen; int64_t decoder_clock; int64_t output_clock; int64_t seek_frame; unsigned int samplerate; unsigned int channels; int64_t length; } ffmpeg_audio_decoder; static int ad_info_ffmpeg(void *sf, struct adinfo *nfo) { ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; if (!priv) return -1; if (nfo) { nfo->sample_rate = priv->samplerate; nfo->channels = priv->channels; nfo->frames = priv->length; if (nfo->sample_rate==0) return -1; nfo->length = (nfo->frames * 1000) / nfo->sample_rate; nfo->bit_rate = priv->formatContext->bit_rate; nfo->bit_depth = 0; nfo->meta_data = NULL; #ifdef WITH_GTK // XXX replace g_* functions with POSIX equiv AVDictionaryEntry *tag = NULL; // Tags in container while ((tag = av_dict_get(priv->formatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { dbg(2, "FTAG: %s=%s", tag->key, tag->value); char * tmp = g_strdup_printf("%s%s%s:%s", nfo->meta_data?nfo->meta_data:"",nfo->meta_data?"\n":"", tag->key, tag->value); if (nfo->meta_data) g_free(nfo->meta_data); nfo->meta_data = tmp; } // Tags in stream tag=NULL; AVStream *stream = priv->formatContext->streams[priv->audioStream]; while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { dbg(2, "STAG: %s=%s", tag->key, tag->value); char * tmp = g_strdup_printf("%s%s%s:%s", nfo->meta_data?nfo->meta_data:"",nfo->meta_data?"\n":"", tag->key, tag->value); if (nfo->meta_data) g_free(nfo->meta_data); nfo->meta_data = tmp; } #endif } return 0; } static void *ad_open_ffmpeg(const char *fn, struct adinfo *nfo) { ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) calloc(1, sizeof(ffmpeg_audio_decoder)); priv->m_tmpBufferStart=NULL; priv->m_tmpBufferLen=0; priv->decoder_clock=priv->output_clock=priv->seek_frame=0; priv->packet.size=0; priv->packet.data=NULL; if (avformat_open_input(&priv->formatContext, fn, NULL, NULL) <0) { dbg(0, "ffmpeg is unable to open file '%s'.", fn); free(priv); return(NULL); } if (avformat_find_stream_info(priv->formatContext, NULL) < 0) { avformat_close_input(&priv->formatContext); dbg(0, "av_find_stream_info failed" ); free(priv); return(NULL); } priv->audioStream = -1; unsigned int i; for (i=0; iformatContext->nb_streams; i++) { if (priv->formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { priv->audioStream = i; break; } } if (priv->audioStream == -1) { dbg(0, "No Audio Stream found in file"); avformat_close_input(&priv->formatContext); free(priv); return(NULL); } priv->codecContext = priv->formatContext->streams[priv->audioStream]->codec; priv->codec = avcodec_find_decoder(priv->codecContext->codec_id); if (priv->codec == NULL) { avformat_close_input(&priv->formatContext); dbg(0, "Codec not supported by ffmpeg"); free(priv); return(NULL); } if (avcodec_open2(priv->codecContext, priv->codec, NULL) < 0) { dbg(0, "avcodec_open failed" ); free(priv); return(NULL); } dbg(2, "ffmpeg - audio tics: %i/%i [sec]",priv->formatContext->streams[priv->audioStream]->time_base.num,priv->formatContext->streams[priv->audioStream]->time_base.den); int64_t len = priv->formatContext->duration - priv->formatContext->start_time; priv->formatContext->flags|=AVFMT_FLAG_GENPTS; priv->formatContext->flags|=AVFMT_FLAG_IGNIDX; priv->samplerate = priv->codecContext->sample_rate; priv->channels = priv->codecContext->channels ; priv->length = (int64_t)( len * priv->samplerate / AV_TIME_BASE ); if (ad_info_ffmpeg((void*)priv, nfo)) { dbg(0, "invalid file info (sample-rate==0)"); free(priv); return(NULL); } dbg(1, "ffmpeg - %s", fn); if (nfo) dbg(1, "ffmpeg - sr:%i c:%i d:%"PRIi64" f:%"PRIi64, nfo->sample_rate, nfo->channels, nfo->length, nfo->frames); return (void*) priv; } static int ad_close_ffmpeg(void *sf) { ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; if (!priv) return -1; avcodec_close(priv->codecContext); avformat_close_input(&priv->formatContext); free(priv); return 0; } static void int16_to_float(int16_t *in, float *out, int num_channels, int num_samples, int out_offset) { int i,ii; for (i=0;ichannels; size_t written = 0; ssize_t ret = 0; while (ret >= 0 && written < frames) { dbg(3,"loop: %i/%i (bl:%lu)",written, frames, priv->m_tmpBufferLen ); if (priv->seek_frame == 0 && priv->m_tmpBufferLen > 0 ) { int s = MIN(priv->m_tmpBufferLen / priv->channels, frames - written ); int16_to_float(priv->m_tmpBufferStart, d, priv->channels, s , written); written += s; priv->output_clock+=s; s = s * priv->channels; priv->m_tmpBufferStart += s; priv->m_tmpBufferLen -= s; ret = 0; } else { priv->m_tmpBufferStart = priv->m_tmpBuffer; priv->m_tmpBufferLen = 0; if (!priv->pkt_ptr || priv->pkt_len <1 ) { if (priv->packet.data) av_free_packet(&priv->packet); ret = av_read_frame(priv->formatContext, &priv->packet); if (ret<0) { dbg(1, "reached end of file."); break; } priv->pkt_len = priv->packet.size; priv->pkt_ptr = priv->packet.data; } if (priv->packet.stream_index != priv->audioStream) { priv->pkt_ptr = NULL; continue; } /* decode all chunks in packet */ int data_size= AVCODEC_MAX_AUDIO_FRAME_SIZE; #if 0 // TODO ffcompat.h -- this works but is not optimal (channels may not be planar/interleaved) AVFrame avf; // TODO statically allocate memset(&avf, 0, sizeof(AVFrame)); // not sure if that is needed int got_frame = 0; ret = avcodec_decode_audio4(priv->codecContext, &avf, &got_frame, &priv->packet); data_size = avf.linesize[0]; memcpy(priv->m_tmpBuffer, avf.data[0], avf.linesize[0] * sizeof(uint8_t)); #else // this was deprecated in LIBAVCODEC_VERSION_MAJOR 53 ret = avcodec_decode_audio3(priv->codecContext, priv->m_tmpBuffer, &data_size, &priv->packet); #endif if (ret < 0 || ret > priv->pkt_len) { #if 0 dbg(0, "audio decode error"); return -1; #endif priv->pkt_len=0; ret=0; continue; } priv->pkt_len -= ret; priv->pkt_ptr += ret; /* sample exact alignment */ if (priv->packet.pts != AV_NOPTS_VALUE) { priv->decoder_clock = priv->samplerate * av_q2d(priv->formatContext->streams[priv->audioStream]->time_base) * priv->packet.pts; } else { dbg(0, "!!! NO PTS timestamp in file"); priv->decoder_clock += (data_size>>1) / priv->channels; } if (data_size>0) { priv->m_tmpBufferLen+= (data_size>>1); // 2 bytes per sample } /* align buffer after seek. */ if (priv->seek_frame > 0) { const int diff = priv->output_clock-priv->decoder_clock; if (diff<0) { /* seek ended up past the wanted sample */ dbg(0, " !!! Audio seek failed."); return -1; } else if (priv->m_tmpBufferLen < (diff*priv->channels)) { /* wanted sample not in current buffer - keep going */ dbg(2, " !!! seeked sample was not in decoded buffer. frames-to-go: %li", diff); priv->m_tmpBufferLen = 0; } else if (diff!=0 && data_size > 0) { /* wanted sample is in current buffer but not at the beginnning */ dbg(2, " !!! sync buffer to seek. (diff:%i)", diff); priv->m_tmpBufferStart+= diff*priv->codecContext->channels; priv->m_tmpBufferLen -= diff*priv->codecContext->channels; #if 1 memmove(priv->m_tmpBuffer, priv->m_tmpBufferStart, priv->m_tmpBufferLen); priv->m_tmpBufferStart = priv->m_tmpBuffer; #endif priv->seek_frame=0; priv->decoder_clock += diff; } else if (data_size > 0) { dbg(2, "Audio exact sync-seek (%"PRIi64" == %"PRIi64")", priv->decoder_clock, priv->seek_frame); priv->seek_frame=0; } else { dbg(0, "Error: no audio data in packet"); } } //dbg(0, "PTS: decoder:%"PRIi64". - want: %"PRIi64, priv->decoder_clock, priv->output_clock); //dbg(0, "CLK: frame: %"PRIi64" T:%.3fs",priv->decoder_clock, (float) priv->decoder_clock/priv->samplerate); } } if (written!=frames) { dbg(2, "short-read"); } return written * priv->channels; } static int64_t ad_seek_ffmpeg(void *sf, int64_t pos) { ffmpeg_audio_decoder *priv = (ffmpeg_audio_decoder*) sf; if (!sf) return -1; if (pos == priv->output_clock) return pos; /* flush internal buffer */ priv->m_tmpBufferLen = 0; priv->seek_frame = pos; priv->output_clock = pos; priv->pkt_len = 0; priv->pkt_ptr = NULL; priv->decoder_clock = 0; #if 0 /* TODO seek at least 1 packet before target. * for mpeg compressed files, the * output may depend on past frames! */ if (pos > 8192) pos -= 8192; else pos = 0; #endif const int64_t timestamp = pos / av_q2d(priv->formatContext->streams[priv->audioStream]->time_base) / priv->samplerate; dbg(2, "seek frame:%"PRIi64" - idx:%"PRIi64, pos, timestamp); av_seek_frame(priv->formatContext, priv->audioStream, timestamp, AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers(priv->codecContext); return pos; } static int ad_eval_ffmpeg(const char *f) { char *ext = strrchr(f, '.'); if (!ext) return 10; // libavformat.. guess_format.. return 40; } #endif static const ad_plugin ad_ffmpeg = { #ifdef HAVE_FFMPEG &ad_eval_ffmpeg, &ad_open_ffmpeg, &ad_close_ffmpeg, &ad_info_ffmpeg, &ad_seek_ffmpeg, &ad_read_ffmpeg #else &ad_eval_null, &ad_open_null, &ad_close_null, &ad_info_null, &ad_seek_null, &ad_read_null #endif }; /* dlopen handler */ const ad_plugin * adp_get_ffmpeg() { #ifdef HAVE_FFMPEG static int ffinit = 0; if (!ffinit) { ffinit=1; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 5, 0) avcodec_init(); #endif av_register_all(); avcodec_register_all(); if(ad_debug_level <= 1) av_log_set_level(AV_LOG_QUIET); else av_log_set_level(AV_LOG_VERBOSE); } #endif return &ad_ffmpeg; }