File indexing completed on 2024-12-01 07:32:47

0001 /*
0002     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 #include "k3bffmpegwrapper.h"
0006 #include "k3bplugin_i18n.h"
0007 
0008 #include <config-k3b.h>
0009 
0010 extern "C" {
0011 /*
0012  Recent versions of FFmpeg uses C99 constant macros which are not present in C++ standard.
0013  The macro __STDC_CONSTANT_MACROS allow C++ to use these macros. Although it's not defined by C++ standard
0014  it's supported by many implementations.
0015  See bug 236036 and discussion: https://lists.ffmpeg.org/pipermail/ffmpeg-devel/2010-May/095488.html
0016  */
0017 #define __STDC_CONSTANT_MACROS
0018 #ifdef NEWFFMPEGAVCODECPATH
0019 #include <libavcodec/avcodec.h>
0020 #include <libavformat/avformat.h>
0021 #else
0022 #include <ffmpeg/avcodec.h>
0023 #include <ffmpeg/avformat.h>
0024 #endif
0025 }
0026 
0027 #include <cstring>
0028 #include <cmath>
0029 
0030 
0031 #ifndef HAVE_FFMPEG_CODEC_MP3
0032 #  define CODEC_ID_MP3 CODEC_ID_MP3LAME
0033 #endif
0034 
0035 K3bFFMpegWrapper* K3bFFMpegWrapper::s_instance = nullptr;
0036 
0037 
0038 class K3bFFMpegFile::Private
0039 {
0040 public:
0041     ::AVFormatContext* formatContext;
0042     const ::AVCodec* codec;
0043     ::AVCodecContext* codecContext;
0044     ::AVStream *audio_stream;
0045 
0046     K3b::Msf length;
0047 
0048     // for decoding. ffmpeg requires 16-byte alignment.
0049     ::AVFrame* frame = nullptr;
0050     ::AVPacket* packet = nullptr;
0051     char* outputBufferPos = nullptr;
0052     int outputBufferSize = 0;
0053     ::AVSampleFormat sampleFormat;
0054     bool isSpacious;
0055 };
0056 
0057 
0058 K3bFFMpegFile::K3bFFMpegFile( const QString& filename )
0059     : m_filename(filename)
0060 {
0061     d = new Private;
0062     d->formatContext = nullptr;
0063     d->codec = nullptr;
0064     d->audio_stream = nullptr;
0065     d->frame = av_frame_alloc();
0066 }
0067 
0068 
0069 K3bFFMpegFile::~K3bFFMpegFile()
0070 {
0071     close();
0072     av_frame_free(&d->frame);
0073     delete d;
0074 }
0075 
0076 
0077 bool K3bFFMpegFile::open()
0078 {
0079     close();
0080 
0081     // open the file
0082     int err = ::avformat_open_input( &d->formatContext, m_filename.toLocal8Bit(), 0, 0 );
0083     if( err < 0 ) {
0084         qDebug() << "(K3bFFMpegFile) unable to open " << m_filename << " with error " << err;
0085         return false;
0086     }
0087 
0088     // analyze the streams
0089     ::avformat_find_stream_info( d->formatContext, 0 );
0090 
0091     // we only handle files containing one audio stream
0092     if( d->formatContext->nb_streams == 1 ) {
0093         d->audio_stream = d->formatContext->streams[0];
0094     } else  {
0095         for (uint i = 0; i < d->formatContext->nb_streams; ++i) {
0096             if (d->formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
0097                 if (!d->audio_stream) {
0098                     d->audio_stream = d->formatContext->streams[i];
0099                 } else {
0100                     d->audio_stream = nullptr;
0101                     qDebug() << "(K3bFFMpegFile) more than one audio stream in " << m_filename;
0102                     return false;
0103                 }
0104             }
0105         }
0106     }
0107 
0108     // get the codec
0109     d->codec = ::avcodec_find_decoder(
0110         d->audio_stream->codecpar->codec_id
0111     );
0112     if( !d->codec ) {
0113         qDebug() << "(K3bFFMpegFile) no codec found for " << m_filename;
0114         return false;
0115     }
0116 
0117     // get the codec context
0118     d->codecContext = ::avcodec_alloc_context3(d->codec);
0119     ::avcodec_parameters_to_context(d->codecContext, d->audio_stream->codecpar);
0120     if(d->codecContext->codec_type != AVMEDIA_TYPE_AUDIO) {
0121         qDebug() << "(K3bFFMpegFile) not a simple audio stream: " << m_filename;
0122         return false;
0123     }
0124 
0125     // open the codec on our context
0126     qDebug() << "(K3bFFMpegFile) found codec for " << m_filename;
0127     if( ::avcodec_open2( d->codecContext, d->codec, 0 ) < 0 ) {
0128         qDebug() << "(K3bFFMpegDecoderFactory) could not open codec.";
0129         return false;
0130     }
0131 
0132     // determine the length of the stream
0133     d->length = K3b::Msf::fromSeconds(
0134         static_cast<double>(d->formatContext->duration) / static_cast<double>(AV_TIME_BASE));
0135 
0136     if( d->length == 0 ) {
0137         qDebug() << "(K3bFFMpegDecoderFactory) invalid length.";
0138         return false;
0139     }
0140 
0141     d->sampleFormat = d->codecContext->sample_fmt;
0142     d->isSpacious = ::av_sample_fmt_is_planar(d->sampleFormat) &&
0143         d->codecContext->channels > 1;
0144     d->packet = ::av_packet_alloc();
0145 
0146     // dump some debugging info
0147     ::av_dump_format( d->formatContext, 0, m_filename.toLocal8Bit(), 0 );
0148 
0149     return true;
0150 }
0151 
0152 
0153 void K3bFFMpegFile::close()
0154 {
0155     d->outputBufferSize = 0;
0156     ::av_packet_free(&d->packet);
0157 
0158     if( d->codec ) {
0159         ::avcodec_close(d->codecContext);
0160         d->codec = nullptr;
0161         ::avcodec_free_context(&d->codecContext);
0162         d->codecContext = nullptr;
0163     }
0164 
0165     if( d->formatContext ) {
0166         ::avformat_close_input( &d->formatContext );
0167         d->formatContext = nullptr;
0168     }
0169 
0170     d->audio_stream = nullptr;
0171 }
0172 
0173 
0174 K3b::Msf K3bFFMpegFile::length() const
0175 {
0176     return d->length;
0177 }
0178 
0179 
0180 int K3bFFMpegFile::sampleRate() const
0181 {
0182     return d->codecContext->sample_rate;
0183 }
0184 
0185 
0186 int K3bFFMpegFile::channels() const
0187 {
0188     return d->codecContext->channels;
0189 }
0190 
0191 
0192 int K3bFFMpegFile::type() const
0193 {
0194     return d->codecContext->codec_id;
0195 }
0196 
0197 
0198 QString K3bFFMpegFile::typeComment() const
0199 {
0200     switch( type() ) {
0201     case AV_CODEC_ID_WAVPACK:
0202         return i18n("WavPack");
0203     case AV_CODEC_ID_APE:
0204         return i18n("Monkey's Audio (APE)");
0205     case AV_CODEC_ID_AAC:
0206         return i18n("Advanced Audio Coding (AAC)");
0207     default:
0208         return QString::fromLocal8Bit( d->codec->name );
0209     }
0210 }
0211 
0212 
0213 QString K3bFFMpegFile::title() const
0214 {
0215     // FIXME: is this UTF8 or something??
0216     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "TITLE", nullptr, 0 );
0217     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
0218 }
0219 
0220 
0221 QString K3bFFMpegFile::author() const
0222 {
0223     // FIXME: is this UTF8 or something??
0224     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "ARTIST", nullptr, 0 );
0225     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
0226 }
0227 
0228 
0229 QString K3bFFMpegFile::comment() const
0230 {
0231     // FIXME: is this UTF8 or something??
0232     AVDictionaryEntry *ade = av_dict_get( d->formatContext->metadata, "COMMENT", nullptr, 0 );
0233     return ade && ade->value && ade->value[0] != '\0' ? QString::fromLocal8Bit( ade->value ) : QString();
0234 }
0235 
0236 
0237 int K3bFFMpegFile::read(char* buf, int bufLen)
0238 {
0239     if (!buf ) {
0240         return -1;
0241     }
0242 
0243     if(d->outputBufferSize <= 0)
0244         d->outputBufferPos = new char[bufLen];
0245 
0246     int ret = fillOutputBuffer();
0247     if (ret <= 0) {
0248         return ret;
0249     }
0250 
0251     int len = qMin(bufLen, ret);
0252     ::memcpy(buf, d->outputBufferPos, len);
0253 
0254     // TODO: only swap if needed
0255     for(int i = 0; i < len-1; i += 2)
0256         std::swap(buf[i], buf[i+1]); // BE -> LE
0257 
0258     d->outputBufferSize -= len;
0259     if(d->outputBufferSize > 0)
0260         d->outputBufferPos += len;
0261     else
0262         delete[] d->outputBufferPos;
0263 
0264     return len;
0265 }
0266 
0267 
0268 int K3bFFMpegFile::readPacket()
0269 {
0270     if( ::av_read_frame( d->formatContext, d->packet ) < 0 ) {
0271         return 0;
0272     }
0273 
0274     return d->packet->size;
0275 }
0276 
0277 
0278 int K3bFFMpegFile::fillOutputBuffer()
0279 {
0280     static int ret = -1;
0281 
0282     // decode if the output buffer is empty
0283     while( d->outputBufferSize <= 0 ) {
0284         d->outputBufferSize = 0;
0285 
0286         if ( ret >= 0 ) {
0287             ret = ::avcodec_receive_frame( d->codecContext, d->frame );
0288 
0289             if ( ret == AVERROR(EAGAIN) || ret == AVERROR_EOF ) {
0290                 ret = -1;
0291                 continue;
0292             }
0293             else if ( ret < 0 ) {
0294                 qDebug() << "(K3bFFMpegFile) decoding failed for " << m_filename;
0295                 return -1;
0296             }
0297 
0298             int nb_s = d->frame->nb_samples;
0299             const int nb_ch = 2;  // copy only 2 channels even if there're more
0300             d->outputBufferSize = nb_s * nb_ch * 2;  // 2 means 2 bytes (16bit)
0301 
0302             if( d->isSpacious ) {
0303                 if( d->sampleFormat == AV_SAMPLE_FMT_FLTP ) {
0304                     constexpr int width = sizeof(float);  // sample width of float audio
0305                     for( int sample = 0; sample < nb_s; sample++ ) {
0306                         for( int ch = 0; ch < nb_ch; ch++ ) {
0307                             float val = *(reinterpret_cast<float*>(
0308                                 d->frame->extended_data[ch] + sample * width));
0309                             val = ::abs(val) > 1 ? ::copysign(1.0, val) : val;
0310                             int16_t result = static_cast<int16_t>(
0311                                 val * 32767.0 + 32768.5) - 32768;
0312                             ::memcpy(
0313                                 d->outputBufferPos + (sample * nb_ch + ch) * 2,
0314                                 &result,
0315                                 2  // 2 is sample width of 16 bit audio
0316                             );
0317                         }
0318                     }
0319                 } else if ( d->sampleFormat == AV_SAMPLE_FMT_S16P ) {
0320                     for(int sample = 0; sample < nb_s; sample++) {
0321                         for(int ch = 0; ch < nb_ch; ch++) {
0322                             ::memcpy(
0323                                 d->outputBufferPos + (sample * nb_ch + ch) * 2,
0324                                 d->frame->extended_data[ch] + sample * 2,
0325                                 2 // 16 bit here as well
0326                             );
0327                         }
0328                     }
0329                 } else {
0330                     qDebug() << "(K3bFFMpegFile) some planar formats are not supported yet";
0331                     return -1;
0332                 }
0333             } else {
0334                 qDebug() << "(K3bFFMpegFile) non planar and monophonic audio support is not implemented";
0335                 return -1;
0336             }
0337         } else {
0338             // make sure we have data to decode
0339             if( readPacket() == 0 ) {
0340                 return 0;
0341             }
0342             ret = ::avcodec_send_packet( d->codecContext, d->packet );
0343             if( ret < 0 ) {
0344                 qDebug() << "(K3bFFMpegFile) error submitting packet to the decoder";
0345                 return -1;
0346             }
0347             continue;
0348         }
0349     }
0350 
0351     return d->outputBufferSize;
0352 }
0353 
0354 
0355 bool K3bFFMpegFile::seek( const K3b::Msf& msf )
0356 {
0357     d->outputBufferSize = 0;
0358 
0359     double seconds = (double)msf.totalFrames()/75.0;
0360     quint64 timestamp = (quint64)(seconds * (double)AV_TIME_BASE);
0361 
0362     // FIXME: do we really need the start_time and why?
0363     return ( ::av_seek_frame( d->formatContext, -1, timestamp + d->formatContext->start_time, 0 ) >= 0 );
0364 }
0365 
0366 
0367 
0368 
0369 
0370 
0371 K3bFFMpegWrapper::K3bFFMpegWrapper()
0372 {
0373     //::av_register_all();
0374 }
0375 
0376 
0377 K3bFFMpegWrapper::~K3bFFMpegWrapper()
0378 {
0379     s_instance = nullptr;
0380 }
0381 
0382 
0383 K3bFFMpegWrapper* K3bFFMpegWrapper::instance()
0384 {
0385     if( !s_instance ) {
0386         s_instance = new K3bFFMpegWrapper();
0387     }
0388 
0389     return s_instance;
0390 }
0391 
0392 
0393 K3bFFMpegFile* K3bFFMpegWrapper::open( const QString& filename ) const
0394 {
0395     K3bFFMpegFile* file = new K3bFFMpegFile( filename );
0396     if( file->open() ) {
0397 #ifndef K3B_FFMPEG_ALL_CODECS
0398         //
0399         // only allow tested formats. ffmpeg seems not to be too reliable with every format.
0400         // mp3 being one of them sadly. Most importantly: allow the libsndfile decoder to do
0401         // its thing.
0402         //
0403         if( file->type() == AV_CODEC_ID_AAC ||
0404             file->type() == AV_CODEC_ID_APE ||
0405             file->type() == AV_CODEC_ID_WAVPACK )
0406 #endif
0407             return file;
0408     }
0409 
0410     delete file;
0411     return nullptr;
0412 }