首页 > 解决方案 > FFmpeg - 音频编码会在音频上产生额外的噪音

问题描述

我正在尝试使用 FFmpeg 拍摄视频(在这种情况下为 MP4)并将其复制为另一个 MP4。这样我就可以掌握解码/编码视频的窍门,并在该过程中继续做其他事情。我的代码基本上采用视频文件,解码视频和音频流,并将视频和音频流编码为输出视频文件。

截至目前,我的代码仅适用于输入文件的视频流。输出文件的视频部分与输入文件的视频部分完全相同。但是,音频部分不是。输出的音频部分包含原始音频,但上面有噪音。可以将其想象为有人对着他们的麦克风尖叫,或者当音频太大而扬声器无法处理时。

我处理视频和音频流的解码/编码过程的方式是相同的,除了 AVCodecContext 设置(视频 --> 帧速率、宽度、高度等;音频 --> 采样速率、通道、 ETC。)。

这是目前我正在使用的代码:

视频结构:

typedef struct Video {
    AVFormatContext* inputContext;
    AVFormatContext* outputContext;
    AVCodec* videoCodec;
    AVCodec* audioCodec;
    AVStream* inputStream;
    AVStream* outputStream;
    AVCodecContext* videoCodecContext_I; // Input
    AVCodecContext* audioCodecContext_I; // Input
    AVCodecContext* videoCodecContext_O; // Output
    AVCodecContext* audioCodecContext_O; // Output
    int videoStream; // Video stream index
    int audioStream; // Audio stream index
} Video;

处理编码/解码的主要代码(我只包括了音频方面,因为视频方面是相同的):

int openVideo(Video* video, char* filename, char* outputFile) {
    video->inputContext = avformat_alloc_context();
    if (!video->inputContext) {
        printf("[ERROR] Failed to allocate input format context\n");
        return -1;
    }
    if (avformat_open_input(&(video->inputContext), filename, NULL, NULL) < 0) {
        printf("[ERROR] Could not open the input file\n");
        return -1;
    }

    if (avformat_find_stream_info(video->inputContext, NULL) < 0) {
        printf("[ERROR] Failed to retrieve input stream info\n");
        return -1;
    }
    avformat_alloc_output_context2(&(video->outputContext), NULL, NULL, outputFile);
    if (!video->outputContext) {
        printf("[ERROR] Failed to create output context\n");
        return -1;
    }
    printf("[OPEN] Video %s opened\n", filename);
    return 0;
}

int prepareStreamInfo(AVCodecContext** codecContext, AVCodec** codec, AVStream* stream) {
    *codec = avcodec_find_decoder(stream->codecpar->codec_id);
    if (!*codec) {
        printf("[ERROR] Failed to find input codec\n");
        return -1;
    }
    *codecContext = avcodec_alloc_context3(*codec);
    if (!codecContext) {
        printf("[ERROR] Failed to allocate memory for input codec context\n");
        return -1;
    }
    if (avcodec_parameters_to_context(*codecContext, stream->codecpar) < 0) {
        printf("[ERROR] Failed to fill input codec context\n");
        return -1;
    }
    if (avcodec_open2(*codecContext, *codec, NULL) < 0) {
        printf("[ERROR] Failed to open input codec\n");
        return -1;
    }
    return 0;
}

int findStreams(Video* video, char* filename, char* outputFile) {
    if (openVideo(video, filename, outputFile) < 0) {
        printf("[ERROR] Video %s failed to open\n", filename);
        return -1;
    }
    for (int i = 0; i < video->inputContext->nb_streams; i++) {
        video->inputStream = video->inputContext->streams[i];
        if (video->inputContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video->videoStream = i;
            if (prepareStreamInfo(&(video->videoCodecContext_I), &(video->videoCodec), video->inputStream) < 0) {
                printf("[ERROR] Could not prepare video stream information\n");
                return -1;video->outputStream->time_base = video->audioCodecContext_O->time_base;
            }
        } else if (video->inputContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            video->audioStream = i;
            if (prepareStreamInfo(&(video->audioCodecContext_I), &(video->audioCodec), video->inputStream) < 0) {
                printf("[ERROR] Could not prepare audio stream information\n");
                return -1;
            }
        }
        video->outputStream = avformat_new_stream(video->outputContext, NULL);
        if (!video->outputStream) {
            printf("[ERROR] Failed allocating output stream\n");
            return -1;
        }
        if (avcodec_parameters_copy(video->outputStream->codecpar, video->inputStream->codecpar) < 0) {
            printf("[ERROR] Failed to copy codec parameters\n");
            return -1;
        }
    }
    if (video->videoStream == -1) {
        printf("[ERROR] Video stream for %s not found\n", filename);
        return -1;
    }
    if (video->audioStream == -1) {
        printf("[ERROR] Audio stream for %s not found\n", filename);
        return -1;
    }
    if (!(video->outputContext->oformat->flags & AVFMT_NOFILE)) {
    if (avio_open(&(video->outputContext->pb), outputFile, AVIO_FLAG_WRITE) < 0) {
      printf("Could not open output file %s", outputFile);
      return -1;
    }
  }
    return 0;
}

int prepareAudioOutStream(Video* video) {
    video->audioCodec = avcodec_find_encoder_by_name("mp2");
    if (!video->audioCodec) {
        printf("[ERROR] Failed to find audio output codec\n");
        return -1;
    }
    video->audioCodecContext_O = avcodec_alloc_context3(video->audioCodec);
    if (!video->audioCodecContext_O) {
        printf("[ERROR] Failed to allocate memory for audio output codec context\n");
        return -1;
    }
    // Quite possibly the issue
    video->audioCodecContext_O->channels = video->audioCodecContext_I->channels;
    video->audioCodecContext_O->channel_layout = av_get_default_channel_layout(video->audioCodecContext_O->channels);
    video->audioCodecContext_O->sample_rate = video->audioCodecContext_I->sample_rate;
    video->audioCodecContext_O->sample_fmt = video->audioCodec->sample_fmts[0];
    video->audioCodecContext_O->bit_rate = video->audioCodecContext_I->bit_rate;
    video->audioCodecContext_O->time_base = video->audioCodecContext_I->time_base;
    video->audioCodecContext_O->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
    if (avcodec_open2(video->audioCodecContext_O, video->audioCodec, NULL) < 0) {
        printf("[ERROR] Failed to open audio output codec\n");
        return -1;
    }
    if (avcodec_parameters_from_context(getAudioStream(video)->codecpar, video->audioCodecContext_O) < 0) {
        printf("[ERROR] Failed to fill audio stream\n");
        return -1;
    }
    return 0;
}

int decodeAudio(Video* video, AVPacket* packet, AVFrame* frame) {
    int response = avcodec_send_packet(video->audioCodecContext_I, packet);
    if (response < 0) {
        printf("[ERROR] Failed to send audio packet to decoder\n");
        return response;
    }
    while (response >= 0) {
        response = avcodec_receive_frame(video->audioCodecContext_I, frame);
        if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
            break;
        } else if (response < 0) {
            printf("[ERROR] Failed to receive audio frame from decoder\n");
            return response;
        }
        if (response >= 0) {
            // Do stuff and encode
            if (encodeAudio(video, frame) < 0) {
                printf("[ERROR] Failed to encode new audio\n");
                return -1;
            }
        }
        av_frame_unref(frame);
    }
    return 0;
}

int encodeAudio(Video* video, AVFrame* frame) {
    AVPacket* packet = av_packet_alloc();
    if (!packet) {
        printf("[ERROR] Could not allocate memory for audio output packet\n");
        return -1;
    }
    int response = avcodec_send_frame(video->audioCodecContext_O, frame);
    if (response < 0) {
        printf("[ERROR] Failed to send audio frame for encoding\n");
        return response;
    }
    while (response >= 0) {
        response = avcodec_receive_packet(video->audioCodecContext_O, packet);
        if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
            break;
        } else if (response < 0) {
            printf("[ERROR] Failed to receive audio packet from encoder\n");
            return response;
        }
        packet->stream_index = video->audioStream;
        video->inputStream = getAudioStream(video);
        video->outputStream = video->outputContext->streams[packet->stream_index];
        packet->pts = av_rescale_q_rnd(packet->pts, video->inputStream->time_base, video->outputStream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        packet->dts = av_rescale_q_rnd(packet->dts, video->inputStream->time_base, video->outputStream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        packet->duration = av_rescale_q(packet->duration, video->inputStream->time_base, video->outputStream->time_base);
        packet->pos = -1;
        //av_packet_rescale_ts(packet, video->inputStream->time_base, video->outputStream->time_base);

        response = av_interleaved_write_frame(video->outputContext, packet);
        if (response < 0) {
            printf("[ERROR] Failed to write audio packet\n");
            break;
        }
    }
    av_packet_unref(packet);
    av_packet_free(&packet);
    return 0;
}

int readFrames(Video* video, AVPacket* packet, AVFrame* frame) {
    if (!packet) {
        printf("[ERROR] Packet not allocated to be read\n");
        return -1;
    }
    if (!frame) {
        printf("[ERROR] Frame not allocated to be read\n");
        return -1;
    }
    if (prepareVideoOutStream(video) < 0) {
        printf("[ERROR] Failed to prepare output video stream\n");
        return -1;
    }
    if (prepareAudioOutStream(video) < 0) {
        printf("[ERROR] Failed to prepare output audio stream\n");
        return -1;
    }
    int frameNum = 0;
    while (av_read_frame(video->inputContext, packet) >= 0) {
        printf("[READ] Reading frame %i\n", frameNum);
        if (packet->stream_index == video->videoStream) {
            if (decodeVideo(video, packet, frame) < 0) {
                printf("[ERROR] Failed to decode and encode video\n");
                return -1;
            }
        } else if (packet->stream_index == video->audioStream) {
            if (decodeAudio(video, packet, frame) < 0) {
                printf("[ERROR] Failed to decode and encode audio\n");
                return -1;
            }
        }
        av_packet_unref(packet);
        frameNum++;
    }
    // Flush encoder
    encodeVideo(video, NULL);
    encodeAudio(video, NULL);
    av_write_trailer(video->outputContext);
    return 0;
}

我运行所有功能的主要方法:

int main(int argc, char* argv[]) {
    Video* video = (Video*)malloc(sizeof(Video));
    initVideo(video);
    if (findStreams(video, argv[1], argv[2]) < 0) {
        printf("[ERROR] Could not find streams\n");
        return -1;
    }

    AVDictionary* dic = NULL;
    if (avformat_write_header(video->outputContext, &dic) < 0) {
        printf("[ERROR] Error while writing header to output file\n");
        return -1;
    }
    AVFrame* frame = av_frame_alloc();
    AVPacket* packet = av_packet_alloc();
    if (readFrames(video, packet, frame) < 0) {
        printf("[ERROR] Failed to read and write new video\n");
        return -1;
    }
    freeVideo(video); // Frees all codecs and contexts and the video
    return 0;
}

我试图布置我的代码,以便可以从上到下阅读它而无需向上滚动。

我意识到在复制视频时,我可以只通过 AVPacket 写入输出文件,但我希望将来能够使用 AVFrame,所以我这样写。我有一种感觉,我的音频行为方式的问题是因为 prepareAudioOutStream() 函数的音频输出 AVCodecContext 。

事实证明,阅读 FFmpeg 文档对这个问题以及其他在线资源帮助不大。我一定遗漏了一些东西(或有一些不需要的东西),所以任何能指引我正确方向的东西都会有所帮助。

谢谢你。

标签: caudioencodingffmpeg

解决方案


我是一名音频工程师,而不是编码员,但我希望这可能会有所帮助。可能发生的是您的位深度被截断;例如,24 位音频被截断为 16 位,这听起来会失真和嘈杂。从最高有效位截断的每个位都会削减6dB 的动态余量。这将增加本底噪声,并将一个响亮但清晰的正弦波稳定地变成一个失真的方波,因为显着的比特减少增加了。

检查重新编码过程中的位深度选项。可能是您的编码器对其位深度有限制。检查源位深度和重新编码的位深度,看看有什么区别。您可以为此使用 VLC 媒体播放器。

还建议您在编码前在信号中留出一些余量(至少 0.1 dB)。预编码的音频可能已经达到最大值,因此重新编码可能会增加一些轻微的失真。

更多信息在这里:

通过截断减少样本位深度

https://www.apple.com/itunes/docs/apple-digital-masters.pdf


推荐阅读