1 /*
2     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 #include "k3bffmpegwrapper.h"
6 #include "k3bplugin_i18n.h"
7 
8 #include <config-k3b.h>
9 
10 extern "C" {
11 /*
12  Recent versions of FFmpeg uses C99 constant macros which are not present in C++ standard.
13  The macro __STDC_CONSTANT_MACROS allow C++ to use these macros. Although it's not defined by C++ standard
14  it's supported by many implementations.
15  See bug 236036 and discussion: https://lists.ffmpeg.org/pipermail/ffmpeg-devel/2010-May/095488.html
16  */
17 #define __STDC_CONSTANT_MACROS
18 #ifdef NEWFFMPEGAVCODECPATH
19 #include <libavcodec/avcodec.h>
20 #include <libavformat/avformat.h>
21 #else
22 #include <ffmpeg/avcodec.h>
23 #include <ffmpeg/avformat.h>
24 #endif
25 }
26 
27 #include <string.h>
28 #include <math.h>
29 
30 
31 #define FFMPEG_CODEC(s) (s->codec)
32 
33 #ifndef HAVE_FFMPEG_AVFORMAT_OPEN_INPUT
34 //      this works because the parameters/options are not used
35 #  define avformat_open_input(c,s,f,o) av_open_input_file(c,s,f,0,o)
36 #endif
37 #ifndef HAVE_FFMPEG_AV_DUMP_FORMAT
38 #  define av_dump_format(c,x,f,y) dump_format(c,x,f,y)
39 #endif
40 #ifndef HAVE_FFMPEG_AVFORMAT_FIND_STREAM_INFO
41 #  define avformat_find_stream_info(c,o) av_find_stream_info(c)
42 #endif
43 #ifndef HAVE_FFMPEG_AVFORMAT_CLOSE_INPUT
44 #  define avformat_close_input(c) av_close_input_file(*c)
45 #endif
46 #ifndef HAVE_FFMPEG_AVCODEC_OPEN2
47 #  define avcodec_open2(a,c,o) avcodec_open(a,c)
48 #endif
49 #ifndef HAVE_FFMPEG_AVMEDIA_TYPE
50 #  define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO
51 #endif
52 #ifndef HAVE_FFMPEG_CODEC_MP3
53 #  define CODEC_ID_MP3 CODEC_ID_MP3LAME
54 #endif
55 
56 K3bFFMpegWrapper* K3bFFMpegWrapper::s_instance = 0;
57 
58 
59 class K3bFFMpegFile::Private
60 {
61 public:
62     ::AVFormatContext* formatContext;
63     ::AVCodec* codec;
64     ::AVStream *audio_stream;
65 
66     K3b::Msf length;
67 
68     // for decoding. ffmpeg requires 16-byte alignment.
69     ::AVFrame* frame;
70     char* outputBufferPos;
71     int outputBufferSize;
72     ::AVPacket packet;
73     quint8* packetData;
74     int packetSize;
75     bool isSpacious;
76     int sampleFormat;
77 };
78 
79 
K3bFFMpegFile(const QString & filename)80 K3bFFMpegFile::K3bFFMpegFile( const QString& filename )
81     : m_filename(filename)
82 {
83     d = new Private;
84     d->formatContext = 0;
85     d->codec = 0;
86     d->audio_stream = nullptr;
87     d->frame = av_frame_alloc();
88 }
89 
90 
~K3bFFMpegFile()91 K3bFFMpegFile::~K3bFFMpegFile()
92 {
93     close();
94     av_frame_free(&d->frame);
95     delete d;
96 }
97 
98 
open()99 bool K3bFFMpegFile::open()
100 {
101     close();
102 
103     // open the file
104     int err = ::avformat_open_input( &d->formatContext, m_filename.toLocal8Bit(), 0, 0 );
105     if( err < 0 ) {
106         qDebug() << "(K3bFFMpegFile) unable to open " << m_filename << " with error " << err;
107         return false;
108     }
109 
110     // analyze the streams
111     ::avformat_find_stream_info( d->formatContext, 0 );
112 
113     // we only handle files containing one audio stream
114     if( d->formatContext->nb_streams == 1 ) {
115         d->audio_stream = d->formatContext->streams[0];
116     } else  {
117         for (uint i = 0; i < d->formatContext->nb_streams; ++i) {
118             if (d->formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
119                 if (!d->audio_stream) {
120                     d->audio_stream = d->formatContext->streams[i];
121                 } else {
122                     d->audio_stream = nullptr;
123                     qDebug() << "(K3bFFMpegFile) more than one audio stream in " << m_filename;
124                     return false;
125                 }
126             }
127         }
128     }
129 
130     // urgh... ugly
131     ::AVCodecContext* codecContext =  FFMPEG_CODEC(d->audio_stream);
132     if( codecContext->codec_type != AVMEDIA_TYPE_AUDIO)
133     {
134         qDebug() << "(K3bFFMpegFile) not a simple audio stream: " << m_filename;
135         return false;
136     }
137 
138     // get the codec
139     d->codec = ::avcodec_find_decoder(codecContext->codec_id);
140     if( !d->codec ) {
141         qDebug() << "(K3bFFMpegFile) no codec found for " << m_filename;
142         return false;
143     }
144 
145     // open the codec on our context
146     qDebug() << "(K3bFFMpegFile) found codec for " << m_filename;
147     if( ::avcodec_open2( codecContext, d->codec, 0 ) < 0 ) {
148         qDebug() << "(K3bFFMpegDecoderFactory) could not open codec.";
149         return false;
150     }
151 
152     // determine the length of the stream
153     d->length = K3b::Msf::fromSeconds( (double)d->formatContext->duration / (double)AV_TIME_BASE );
154 
155     if( d->length == 0 ) {
156         qDebug() << "(K3bFFMpegDecoderFactory) invalid length.";
157         return false;
158     }
159 
160     d->sampleFormat = d->audio_stream->codecpar->format;
161     d->isSpacious = ::av_sample_fmt_is_planar((AVSampleFormat)d->sampleFormat) && d->audio_stream->codecpar->channels > 1;
162 
163     // dump some debugging info
164     ::av_dump_format( d->formatContext, 0, m_filename.toLocal8Bit(), 0 );
165 
166     return true;
167 }
168 
169 
close()170 void K3bFFMpegFile::close()
171 {
172     d->outputBufferSize = 0;
173     d->packetSize = 0;
174     d->packetData = 0;
175 
176     if( d->codec ) {
177         ::avcodec_close( FFMPEG_CODEC(d->audio_stream) );
178         d->codec = 0;
179     }
180 
181     if( d->formatContext ) {
182         ::avformat_close_input( &d->formatContext );
183         d->formatContext = 0;
184     }
185 
186     d->audio_stream = nullptr;
187 }
188 
189 
length() const190 K3b::Msf K3bFFMpegFile::length() const
191 {
192     return d->length;
193 }
194 
195 
sampleRate() const196 int K3bFFMpegFile::sampleRate() const
197 {
198     return d->audio_stream->codecpar->sample_rate;
199 }
200 
201 
channels() const202 int K3bFFMpegFile::channels() const
203 {
204     return d->audio_stream->codecpar->channels;
205 }
206 
207 
type() const208 int K3bFFMpegFile::type() const
209 {
210     return d->audio_stream->codecpar->codec_id;
211 }
212 
213 
typeComment() const214 QString K3bFFMpegFile::typeComment() const
215 {
216     switch( type() ) {
217     case AV_CODEC_ID_WMAV1:
218         return i18n("Windows Media v1");
219     case AV_CODEC_ID_WMAV2:
220         return i18n("Windows Media v2");
221     case AV_CODEC_ID_WAVPACK:
222         return i18n("WavPack");
223     case AV_CODEC_ID_APE:
224         return i18n("Monkey's Audio (APE)");
225     case AV_CODEC_ID_AAC:
226         return i18n("Advanced Audio Coding (AAC)");
227     default:
228         return QString::fromLocal8Bit( d->codec->name );
229     }
230 }
231 
232 
title() const233 QString K3bFFMpegFile::title() const
234 {
235     // FIXME: is this UTF8 or something??
236     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "TITLE", NULL, 0 );
237     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
238 }
239 
240 
author() const241 QString K3bFFMpegFile::author() const
242 {
243     // FIXME: is this UTF8 or something??
244     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "ARTIST", NULL, 0 );
245     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
246 }
247 
248 
comment() const249 QString K3bFFMpegFile::comment() const
250 {
251     // FIXME: is this UTF8 or something??
252     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "COMMENT", NULL, 0 );
253     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
254 }
255 
256 
read(char * buf,int bufLen)257 int K3bFFMpegFile::read(char* buf, int bufLen)
258 {
259     if (!buf || !d->outputBufferPos)
260         return -1;
261 
262     int ret = fillOutputBuffer();
263     if (ret <= 0) {
264         return ret;
265     }
266 
267     int len = qMin(bufLen, d->outputBufferSize);
268     ::memcpy(buf, d->outputBufferPos, len);
269 
270     if(d->isSpacious && bufLen > d->outputBufferSize)
271         delete[] d->outputBufferPos; // clean up allocated space
272 
273     // TODO: only swap if needed
274     for(int i=0; i<len-1; i+=2)
275         qSwap(buf[i], buf[i+1]); // BE -> LE
276 
277     d->outputBufferSize -= len;
278     if(d->outputBufferSize > 0)
279         d->outputBufferPos += len;
280     return len;
281 }
282 
283 
284 // fill d->packetData with data to decode
readPacket()285 int K3bFFMpegFile::readPacket()
286 {
287     if( d->packetSize <= 0 ) {
288         ::av_init_packet( &d->packet );
289 
290         if( ::av_read_frame( d->formatContext, &d->packet ) < 0 ) {
291             return 0;
292         }
293         d->packetSize = d->packet.size;
294         d->packetData = d->packet.data;
295     }
296 
297     return d->packetSize;
298 }
299 
300 
301 // decode data in d->packetData and fill d->outputBuffer
fillOutputBuffer()302 int K3bFFMpegFile::fillOutputBuffer()
303 {
304     // decode if the output buffer is empty
305     while(d->outputBufferSize <= 0) {
306 
307         // make sure we have data to decode
308         if( readPacket() == 0 ) {
309             return 0;
310         }
311 
312         int gotFrame = 0;
313         int len = ::avcodec_decode_audio4(
314             FFMPEG_CODEC(d->audio_stream),
315             d->frame,
316             &gotFrame,
317             &d->packet );
318 
319         if( d->packetSize <= 0 || len < 0 )
320             ::av_packet_unref( &d->packet );
321         if( len < 0 ) {
322             qDebug() << "(K3bFFMpegFile) decoding failed for " << m_filename;
323             return -1;
324         }
325 
326         if (gotFrame) {
327             int nb_s = d->frame->nb_samples;
328             int nb_ch = 2; // copy only two channels even if there're more
329             d->outputBufferSize = nb_s * nb_ch * 2; // 2 means 2 bytes (16bit)
330             d->outputBufferPos = reinterpret_cast<char*>(
331                 d->frame->extended_data[0]);
332             if(d->isSpacious) {
333                 d->outputBufferPos = new char[d->outputBufferSize];
334                 if(d->sampleFormat == AV_SAMPLE_FMT_FLTP) {
335                     int width = sizeof(float); // sample width of float audio
336                     for(int sample=0; sample<nb_s; sample++) {
337                         for(int ch=0; ch<nb_ch; ch++) {
338                             float val = *(reinterpret_cast<float*>(
339                                 d->frame->extended_data[ch] + sample * width));
340                             val = ::abs(val) > 1 ? ::copysign(1.0, val) : val;
341                             int16_t result = static_cast<int16_t>(
342                                 val * 32767.0 + 32768.5) - 32768;
343                             ::memcpy(d->outputBufferPos + (sample*nb_ch+ch) * 2,
344                                      &result,
345                                      2); // 2 is sample width of 16 bit audio
346                         }
347                     }
348                 } else {
349                     for(int sample=0; sample<nb_s; sample++) {
350                         for(int ch=0; ch<nb_ch; ch++) {
351                             ::memcpy(d->outputBufferPos + (sample*nb_ch+ch) * 2,
352                                      d->frame->extended_data[ch] + sample * 2,
353                                      2); // 16 bit here as well
354                         }
355                     }
356                 }
357             }
358         }
359         d->packetSize -= len;
360         d->packetData += len;
361     }
362 
363     return d->outputBufferSize;
364 }
365 
366 
seek(const K3b::Msf & msf)367 bool K3bFFMpegFile::seek( const K3b::Msf& msf )
368 {
369     d->outputBufferSize = 0;
370     d->packetSize = 0;
371 
372     double seconds = (double)msf.totalFrames()/75.0;
373     quint64 timestamp = (quint64)(seconds * (double)AV_TIME_BASE);
374 
375     // FIXME: do we really need the start_time and why?
376     return ( ::av_seek_frame( d->formatContext, -1, timestamp + d->formatContext->start_time, 0 ) >= 0 );
377 }
378 
379 
380 
381 
382 
383 
K3bFFMpegWrapper()384 K3bFFMpegWrapper::K3bFFMpegWrapper()
385 {
386     ::av_register_all();
387 }
388 
389 
~K3bFFMpegWrapper()390 K3bFFMpegWrapper::~K3bFFMpegWrapper()
391 {
392     s_instance = 0;
393 }
394 
395 
instance()396 K3bFFMpegWrapper* K3bFFMpegWrapper::instance()
397 {
398     if( !s_instance ) {
399         s_instance = new K3bFFMpegWrapper();
400     }
401 
402     return s_instance;
403 }
404 
405 
open(const QString & filename) const406 K3bFFMpegFile* K3bFFMpegWrapper::open( const QString& filename ) const
407 {
408     K3bFFMpegFile* file = new K3bFFMpegFile( filename );
409     if( file->open() ) {
410 #ifndef K3B_FFMPEG_ALL_CODECS
411         //
412         // only allow tested formats. ffmpeg seems not to be too reliable with every format.
413         // mp3 being one of them sadly. Most importantly: allow the libsndfile decoder to do
414         // its thing.
415         //
416         if( file->type() == AV_CODEC_ID_WMAV1 ||
417             file->type() == AV_CODEC_ID_WMAV2 ||
418             file->type() == AV_CODEC_ID_AAC ||
419             file->type() == AV_CODEC_ID_APE ||
420             file->type() == AV_CODEC_ID_WAVPACK )
421 #endif
422             return file;
423     }
424 
425     delete file;
426     return 0;
427 }
428