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