【问题标题】:C++ FFmpeg distorted sound when converting audioC++ FFmpeg 转换音频时声音失真
【发布时间】:2020-06-08 06:06:34
【问题描述】:

我正在使用 FFmpeg 库生成 MP4 文件,其中包含来自各种文件(如 MP3、WAV、OGG)的音频,但我遇到了一些麻烦(我也将视频放在那里,但为了简单起见,我'我忽略了这个问题,因为我已经开始工作了)。我当前的代码打开一个音频文件,解码内容并将其转换为 MP4 容器,最后将其作为交错帧写入目标文件。

它适用于大多数 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);

我也尝试为传出帧设置适当的 pts 值,但这似乎根本不会影响音质。

我也不确定如何/是否应该分配转换后的数据,可以使用 av_samples_alloc 吗? avcodec_fill_audio_frame 呢?我在正确的轨道上吗?

感谢任何输入(如果您想听到失真,我也可以发送导出的 MP4 文件)。

【问题讨论】:

    标签: audio ffmpeg decode mp4


    【解决方案1】:
    if (avcodec_encode_audio2(audioCodecContext, &outPacket, audioFrameConverted, &frameFinished) < 0)
                    die("Error encoding audio frame");
    

    您似乎假设编码器会吃掉所有提交的样本 - 但事实并非如此。它也不会在内部缓存它们。它会吃掉特定数量的样本(AVCodecContext.frame_size),其余的应该在下次调用 avcodec_encode_audio2() 时重新提交。

    [编辑]

    好的,所以您编辑的代码更好,但还没有。您仍然假设解码器将至少为每次调用 avcodec_decode_audioN() 输出 frame_size 样本(重新采样后),但情况可能并非如此。如果发生这种情况(对于 ogg 确实如此),您的 avcodec_encode_audioN() 调用将编码一个不完整的输入缓冲区(因为您说它有 frame_size 样本,但它没有)。同样,您的代码也不会处理解码器输出的数字明显大于编码器预期的 frame_size(如 10*frame_size)的情况,在这种情况下您会出现溢出 - 基本上是您的 1:1 解码/编码映射是您问题的主要根源。

    作为一种解决方案,将 swrContext 视为一个 FIFO,您可以在其中输入所有解码器样本,然后对其进行循环,直到剩余的样本数少于 frame_size。我将留给您学习如何处理流结束,因为您需要将缓存的样本从解码器中刷新(通过调用 avcodec_decode_audioN() 和 AVPacket where .data = NULL and .size = 0),刷新 swrContext(通过调用 swr_context() 直到它返回 0)以及刷新编码器(通过向它提供 NULL AVFrames 直到它返回 .size = 0 的 AVPacket)。现在你可能会得到一个结尾被稍微截断的输出文件。这应该不难弄清楚。

    此代码适用于我的 m4a/ogg/mp3 到 m4a/aac 转换:

    #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;
    }
    

    【讨论】:

    • 但我将帧大小设置为样本数量,正如您在我的代码中看到的那样。那它不应该吃光一切吗?还是我不明白什么?你能告诉我如何让它工作吗?
    • 您不能设置 frame_size,它是由编解码器提供给您的信息。认为它是只读的。可以肯定的是,检查 avcodec_encode_audioN() 的返回值,看看有多少样本被真正编码。您会注意到它与输入样本数不匹配。
    • 它为我返回 0,根据文档,这意味着成功。我应该检查 outPacket.size 吗?我发现的所有示例都没有这样做,它们只是检查 frameFinished,在我的情况下它总是给出 1。
    • 哦,是的,你是对的,它返回 0。但是你不能分配 frame_size,并且应该假设它消耗了 frame_size 样本。
    • 我已经更改了代码(不再更改 frame_size),但我仍然遇到同样的问题:pastebin.com/ENzeaHpU 还有更多提示吗?
    【解决方案2】:

    我想包括一些我在使用上述代码时发现的东西。 我有一个文件陷入无限循环。原因是该文件的采样率为 48000,而代码将其更改为 44100。这导致它总是有额外的 outSamples。 swr_convert & 不会抓住它们。所以我最终改变了 add_audio_stream 以匹配输入流的采样率。

            c->sample_rate = fileCodecContext->sample_rate;
    

    我还必须生成 wav 文件作为输出。它的帧大小为 0。所以我在几次测试后选择了一个数字,我选择了 32。我注意到如果我太大(例如 128),我会得到音频故障。

     if (audioFrameConverted->nb_samples <= 0) audioFrameConverted->nb_samples = 32; //wav files have a 0 
    

    更改了跳出循环的 if 语句以检查 nb_samples if frame_size 是否为 0。

                                if ((outSamples < audioCodecContext->frame_size * audioCodecContext->channels) || audioCodecContext->frame_size==0 && (outSamples < audioFrameConverted->nb_samples * audioCodecContext->channels)) break; // see comments, thanks to @dajuric for fixing this
    

    我在测试输出到 ogg 文件时也出现了一个故障,其中时间戳数据丢失,因此文件无法在 vlc 中正确播放。我添加的几行代码对此有所帮助。

            out_audioStream->time_base = in_audioStream->time_base; // entered before avio_open.
                            outPacket.dts = audioFrameDecoded->pkt_dts;//rest after avcodec_encode_audio2
                            outPacket.pts = audioFrameDecoded->pkt_pts;
                            av_packet_rescale_ts(&outPacket, in_audioStream->time_base, out_audioStream->time_base);
    

    变量可能有点不同 我将代码转换为 c#。认为这可能会对某人有所帮助。

    【讨论】:

      【解决方案3】:

      实际上 swr_convert 并不能解决这个问题,请尝试使用 swr_convert_frame 代替。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-01-14
        • 1970-01-01
        • 1970-01-01
        • 2016-04-15
        • 1970-01-01
        • 2010-11-20
        • 2019-03-13
        相关资源
        最近更新 更多