2015-08-17 4 views
1

Я использую библиотеку FFmpeg для создания файлов MP4, содержащих аудио из различных файлов, таких как MP3, WAV, OGG, но у меня возникают некоторые проблемы (я также помещаю видео в там, но для простоты я опускаю это для этого вопроса, так как у меня это работает). Мой текущий код открывает звуковой файл, декодирует содержимое и преобразует его в контейнер MP4 и, наконец, записывает в целевой файл в виде чередующихся кадров.C++ FFmpeg искаженный звук при преобразовании звука

Он отлично работает для большинства MP3-файлов, но при вводе WAV или OGG звук в результирующем MP4 слегка искажен и часто воспроизводится с неправильной скоростью (во много раз быстрее или медленнее).

Я рассмотрел множество примеров использования функций преобразования (swr_convert), но я не могу избавиться от шума в экспортированном аудио.

Вот как добавить аудиопоток в MP4 (outContext является AVFormatContext для выходного файла):

audioCodec = avcodec_find_encoder(outContext->oformat->audio_codec); 
if (!audioCodec) 
    die("Could not find audio encoder!"); 


// Start stream 
audioStream = avformat_new_stream(outContext, audioCodec); 
if (!audioStream) 
    die("Could not allocate audio stream!"); 

audioCodecContext = audioStream->codec; 
audioStream->id = 1; 


// Setup 
audioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16; 
audioCodecContext->bit_rate = 128000; 
audioCodecContext->sample_rate = 44100; 
audioCodecContext->channels = 2; 
audioCodecContext->channel_layout = AV_CH_LAYOUT_STEREO; 


// Open the codec 
if (avcodec_open2(audioCodecContext, audioCodec, NULL) < 0) 
    die("Could not open audio codec"); 

И открыть звуковой файл MP3/WAV/OGG (от переменного имени файла) ...

// Create contex 
formatContext = avformat_alloc_context(); 
if (avformat_open_input(&formatContext, filename, NULL, NULL)<0) 
    die("Could not open file"); 


// Find info 
if (avformat_find_stream_info(formatContext, 0)<0) 
    die("Could not find file info"); 

av_dump_format(formatContext, 0, filename, false); 


// Find audio stream 
streamId = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); 
if (streamId < 0) 
    die("Could not find Audio Stream"); 

codecContext = formatContext->streams[streamId]->codec; 


// Find decoder 
codec = avcodec_find_decoder(codecContext->codec_id); 
if (codec == NULL) 
    die("cannot find codec!"); 


// Open codec 
if (avcodec_open2(codecContext, codec, 0)<0) 
    die("Codec cannot be found"); 


// Set up resample context 
swrContext = swr_alloc(); 
if (!swrContext) 
    die("Failed to alloc swr context"); 

av_opt_set_int(swrContext, "in_channel_count", codecContext->channels, 0); 
av_opt_set_int(swrContext, "in_channel_layout", codecContext->channel_layout, 0); 
av_opt_set_int(swrContext, "in_sample_rate", codecContext->sample_rate, 0); 
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", codecContext->sample_fmt, 0); 

av_opt_set_int(swrContext, "out_channel_count", audioCodecContext->channels, 0); 
av_opt_set_int(swrContext, "out_channel_layout", audioCodecContext->channel_layout, 0); 
av_opt_set_int(swrContext, "out_sample_rate", audioCodecContext->sample_rate, 0); 
av_opt_set_sample_fmt(swrContext, "out_sample_fmt", audioCodecContext->sample_fmt, 0); 

if (swr_init(swrContext)) 
    die("Failed to init swr context"); 

Наконец, для декодирования + конвертировать + закодировать ...

// Allocate and init re-usable frames 
audioFrameDecoded = av_frame_alloc(); 
if (!audioFrameDecoded) 
     die("Could not allocate audio frame"); 

audioFrameDecoded->format = fileCodecContext->sample_fmt; 
audioFrameDecoded->channel_layout = fileCodecContext->channel_layout; 
audioFrameDecoded->channels = fileCodecContext->channels; 
audioFrameDecoded->sample_rate = fileCodecContext->sample_rate; 

audioFrameConverted = av_frame_alloc(); 
if (!audioFrameConverted) 
     die("Could not allocate audio frame"); 

audioFrameConverted->nb_samples = audioCodecContext->frame_size; 
audioFrameConverted->format = audioCodecContext->sample_fmt; 
audioFrameConverted->channel_layout = audioCodecContext->channel_layout; 
audioFrameConverted->channels = audioCodecContext->channels; 
audioFrameConverted->sample_rate = audioCodecContext->sample_rate; 

AVPacket inPacket; 
av_init_packet(&inPacket); 
inPacket.data = NULL; 
inPacket.size = 0; 

int frameFinished = 0; 

while (av_read_frame(formatContext, &inPacket) >= 0) { 

     if (inPacket.stream_index == streamId) { 

       int len = avcodec_decode_audio4(fileCodecContext, audioFrameDecoded, &frameFinished, &inPacket); 

       if (frameFinished) { 

         // Convert 

         uint8_t *convertedData=NULL; 

         if (av_samples_alloc(&convertedData, 
              NULL, 
              audioCodecContext->channels, 
              audioFrameConverted->nb_samples, 
              audioCodecContext->sample_fmt, 0) < 0) 
           die("Could not allocate samples"); 

         int outSamples = swr_convert(swrContext, 
                &convertedData, 
                audioFrameConverted->nb_samples, 
                (const uint8_t **)audioFrameDecoded->data, 
                audioFrameDecoded->nb_samples); 
         if (outSamples < 0) 
           die("Could not convert"); 

         size_t buffer_size = av_samples_get_buffer_size(NULL, 
                     audioCodecContext->channels, 
                     audioFrameConverted->nb_samples, 
                     audioCodecContext->sample_fmt, 
                     0); 
         if (buffer_size < 0) 
           die("Invalid buffer size"); 

         if (avcodec_fill_audio_frame(audioFrameConverted, 
                audioCodecContext->channels, 
                audioCodecContext->sample_fmt, 
                convertedData, 
                buffer_size, 
                0) < 0) 
           die("Could not fill frame"); 

         AVPacket outPacket; 
         av_init_packet(&outPacket); 
         outPacket.data = NULL; 
         outPacket.size = 0; 

         if (avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) < 0) 
           die("Error encoding audio frame"); 

         if (frameFinished) { 
           outPacket.stream_index = audioStream->index; 

           if (av_interleaved_write_frame(outContext, &outPacket) != 0) 
             die("Error while writing audio frame"); 

           av_free_packet(&outPacket); 
         } 
       } 
     } 
} 

av_frame_free(&audioFrameConverted); 
av_frame_free(&audioFrameDecoded); 
av_free_packet(&inPacket); 

Я также попытался установить приложение соответствующие значения очков для исходящих кадров, но это никак не влияет на качество звука.

Я также не знаю, как/если я должен распределять преобразованные данные, можно ли использовать av_samples_alloc для этого? Как насчет avcodec_fill_audio_frame? Я на правильном пути?

Любой вход оценен (я также могу отправить экспортированные MP4, если необходимо, если вы хотите услышать искажение).

ответ

5
if (avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) < 0) 
       die("Error encoding audio frame"); 

Вы, кажется, предполагая, что кодер будет есть все представленные образцы - это не делает. Он также не кэширует их внутренне. Он будет потреблять определенное количество выборок (AVCodecContext.frame_size), а остальные должны быть повторно отправлены при следующем вызове avcodec_encode_audio2().

[править]

нормально, так что ваш отредактированный код лучше, но пока не существует. Вы по-прежнему предполагаете, что декодер выведет по меньшей мере кадры frame_size для каждого вызова на avcodec_decode_audioN() (после повторной выборки), что может и не быть. Если это произойдет (и это произойдет, для ogg), ваш вызов avcodec_encode_audioN() будет кодировать неполный входной буфер (потому что вы говорите, что у него есть образцы frame_size, но это не так). Аналогичным образом, ваш код также не касается случаев, когда декодер выводит число, значительно большее, чем frame_size (например, 10 * frame_size), ожидаемое кодером, и в этом случае вы получите перерасход - в основном, ваше кодирование декодирования/кодирования 1: 1 является основным источником вашей проблемы.

В качестве решения рассмотрите swrContext FIFO, в который вы вводите все образцы декодера, и перебирайте его, пока не получите меньше кадров frame_size. Я оставлю это для вас, чтобы узнать, как бороться с конечным потоком, потому что вам нужно очистить кэшированные сэмплы из декодера (вызывая avcodec_decode_audioN() с помощью AVPacket, где .data = NULL и .size = 0), очистите swrContext (вызывая swr_context() до тех пор, пока он не вернет 0), а также запустите кодировщик (путем подачи его NULL AVFrames, пока он не вернет AVPacket с .size = 0).Прямо сейчас вы, вероятно, получите выходной файл, где конец немного усечен. Это не сложно понять.

Этот код работает для меня m4a/OGG/mp3 для преобразования m4a/ААС:

#include "libswresample/swresample.h" 
#include "libavcodec/avcodec.h" 
#include "libavformat/avformat.h" 
#include "libavutil/opt.h" 

#include <stdio.h> 
#include <stdlib.h> 

static void die(char *str) { 
    fprintf(stderr, "%s\n", str); 
    exit(1); 
} 

static AVStream *add_audio_stream(AVFormatContext *oc, enum AVCodecID codec_id) 
{ 
    AVCodecContext *c; 
    AVCodec *encoder = avcodec_find_encoder(codec_id); 
    AVStream *st = avformat_new_stream(oc, encoder); 

    if (!st) die("av_new_stream"); 

    c = st->codec; 
    c->codec_id = codec_id; 
    c->codec_type = AVMEDIA_TYPE_AUDIO; 

    /* put sample parameters */ 
    c->bit_rate = 64000; 
    c->sample_rate = 44100; 
    c->channels = 2; 
    c->sample_fmt = encoder->sample_fmts[0]; 
    c->channel_layout = AV_CH_LAYOUT_STEREO; 

    // some formats want stream headers to be separate 
    if(oc->oformat->flags & AVFMT_GLOBALHEADER) 
     c->flags |= CODEC_FLAG_GLOBAL_HEADER; 

    return st; 
} 

static void open_audio(AVFormatContext *oc, AVStream *st) 
{ 
    AVCodecContext *c = st->codec; 
    AVCodec *codec; 

    /* find the audio encoder */ 
    codec = avcodec_find_encoder(c->codec_id); 
    if (!codec) die("avcodec_find_encoder"); 

    /* open it */ 
    AVDictionary *dict = NULL; 
    av_dict_set(&dict, "strict", "+experimental", 0); 
    int res = avcodec_open2(c, codec, &dict); 
    if (res < 0) die("avcodec_open"); 
} 

int main(int argc, char *argv[]) { 
    av_register_all(); 

    if (argc != 3) { 
     fprintf(stderr, "%s <in> <out>\n", argv[0]); 
     exit(1); 
    } 

    // Allocate and init re-usable frames 
    AVCodecContext *fileCodecContext, *audioCodecContext; 
    AVFormatContext *formatContext, *outContext; 
    AVStream *audioStream; 
    SwrContext *swrContext; 
    int streamId; 

    // input file 
    const char *file = argv[1]; 
    int res = avformat_open_input(&formatContext, file, NULL, NULL); 
    if (res != 0) die("avformat_open_input"); 
    res = avformat_find_stream_info(formatContext, NULL); 
    if (res < 0) die("avformat_find_stream_info"); 
    AVCodec *codec; 
    res = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); 
    if (res < 0) die("av_find_best_stream"); 
    streamId = res; 
    fileCodecContext = avcodec_alloc_context3(codec); 
    avcodec_copy_context(fileCodecContext, formatContext->streams[streamId]->codec); 
    res = avcodec_open2(fileCodecContext, codec, NULL); 
    if (res < 0) die("avcodec_open2"); 

    // output file 
    const char *outfile = argv[2]; 
    AVOutputFormat *fmt = fmt = av_guess_format(NULL, outfile, NULL); 
    if (!fmt) die("av_guess_format"); 
    outContext = avformat_alloc_context(); 
    outContext->oformat = fmt; 
    audioStream = add_audio_stream(outContext, fmt->audio_codec); 
    open_audio(outContext, audioStream); 
    res = avio_open2(&outContext->pb, outfile, AVIO_FLAG_WRITE, NULL, NULL); 
    if (res < 0) die("url_fopen"); 
    avformat_write_header(outContext, NULL); 
    audioCodecContext = audioStream->codec; 

    // resampling 
    swrContext = swr_alloc(); 
    av_opt_set_channel_layout(swrContext, "in_channel_layout", fileCodecContext->channel_layout, 0); 
    av_opt_set_channel_layout(swrContext, "out_channel_layout", audioCodecContext->channel_layout, 0); 
    av_opt_set_int(swrContext, "in_sample_rate", fileCodecContext->sample_rate, 0); 
    av_opt_set_int(swrContext, "out_sample_rate", audioCodecContext->sample_rate, 0); 
    av_opt_set_sample_fmt(swrContext, "in_sample_fmt", fileCodecContext->sample_fmt, 0); 
    av_opt_set_sample_fmt(swrContext, "out_sample_fmt", audioCodecContext->sample_fmt, 0); 
    res = swr_init(swrContext); 
    if (res < 0) die("swr_init"); 

    AVFrame *audioFrameDecoded = av_frame_alloc(); 
    if (!audioFrameDecoded) 
     die("Could not allocate audio frame"); 

    audioFrameDecoded->format = fileCodecContext->sample_fmt; 
    audioFrameDecoded->channel_layout = fileCodecContext->channel_layout; 
    audioFrameDecoded->channels = fileCodecContext->channels; 
    audioFrameDecoded->sample_rate = fileCodecContext->sample_rate; 

    AVFrame *audioFrameConverted = av_frame_alloc(); 
    if (!audioFrameConverted) die("Could not allocate audio frame"); 

    audioFrameConverted->nb_samples = audioCodecContext->frame_size; 
    audioFrameConverted->format = audioCodecContext->sample_fmt; 
    audioFrameConverted->channel_layout = audioCodecContext->channel_layout; 
    audioFrameConverted->channels = audioCodecContext->channels; 
    audioFrameConverted->sample_rate = audioCodecContext->sample_rate; 

    AVPacket inPacket; 
    av_init_packet(&inPacket); 
    inPacket.data = NULL; 
    inPacket.size = 0; 

    int frameFinished = 0; 

    while (av_read_frame(formatContext, &inPacket) >= 0) { 
     if (inPacket.stream_index == streamId) { 
      int len = avcodec_decode_audio4(fileCodecContext, audioFrameDecoded, &frameFinished, &inPacket); 

      if (frameFinished) { 

       // Convert 

       uint8_t *convertedData=NULL; 

       if (av_samples_alloc(&convertedData, 
          NULL, 
          audioCodecContext->channels, 
          audioFrameConverted->nb_samples, 
          audioCodecContext->sample_fmt, 0) < 0) 
        die("Could not allocate samples"); 

       int outSamples = swr_convert(swrContext, NULL, 0, 
          //&convertedData, 
          //audioFrameConverted->nb_samples, 
          (const uint8_t **)audioFrameDecoded->data, 
          audioFrameDecoded->nb_samples); 
       if (outSamples < 0) die("Could not convert"); 

       for (;;) { 
        outSamples = swr_get_out_samples(swrContext, 0); 
        if (outSamples < audioCodecContext->frame_size * audioCodecContext->channels) break; // see comments, thanks to @dajuric for fixing this 

        outSamples = swr_convert(swrContext, 
               &convertedData, 
               audioFrameConverted->nb_samples, NULL, 0); 

        size_t buffer_size = av_samples_get_buffer_size(NULL, 
            audioCodecContext->channels, 
            audioFrameConverted->nb_samples, 
            audioCodecContext->sample_fmt, 
            0); 
        if (buffer_size < 0) die("Invalid buffer size"); 

        if (avcodec_fill_audio_frame(audioFrameConverted, 
          audioCodecContext->channels, 
          audioCodecContext->sample_fmt, 
          convertedData, 
          buffer_size, 
          0) < 0) 
         die("Could not fill frame"); 

        AVPacket outPacket; 
        av_init_packet(&outPacket); 
        outPacket.data = NULL; 
        outPacket.size = 0; 

        if (avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) < 0) 
         die("Error encoding audio frame"); 

        if (frameFinished) { 
         outPacket.stream_index = audioStream->index; 

         if (av_interleaved_write_frame(outContext, &outPacket) != 0) 
          die("Error while writing audio frame"); 

         av_free_packet(&outPacket); 
        } 
       } 
      } 
     } 
    } 

    swr_close(swrContext); 
    swr_free(&swrContext); 
    av_frame_free(&audioFrameConverted); 
    av_frame_free(&audioFrameDecoded); 
    av_free_packet(&inPacket); 
    av_write_trailer(outContext); 
    avio_close(outContext->pb); 
    avcodec_close(fileCodecContext); 
    avcodec_free_context(&fileCodecContext); 
    avformat_close_input(&formatContext); 

    return 0; 
} 
+0

Но я устанавливаю размер кадра на количество образцов, как вы можете увидеть в моем коде. Разве он не должен есть все? Или я что-то не понимаю? Можете ли вы показать, как я могу заставить его работать? – david

+0

Вы не можете установить frame_size, это информативно предоставлено вам кодеком. Учитывайте его только для чтения. Конечно, проверьте возвращаемое значение avcodec_encode_audioN(), чтобы узнать, сколько проб было действительно закодировано. Вы заметите, что оно не соответствует количеству входных выборок. –

+0

Он возвращает 0 для меня, что согласно документации означает успех. Должен ли я проверять с помощью outPacket.size? Ни один из примеров, которые я нашел, не делает этого, они просто проверяют с помощью frameFinished, который всегда дает 1 в моем случае. – david