使用Libav的视频原始音频解码被切碎

时间:2020-06-23 16:49:42

标签: c ffmpeg pcm libav

我目前正在使用libav将视频的音频流提取到原始PCM文件中。

此代码适用于mp3,但是当我尝试播放mp4视频时,在Audacity上导入的原始格式显示出奇怪的0到-1之间的规则下降线。

Audacity Waveform

这是我的实现方式。

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

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>

int decode_raw(AVFormatContext *format_ctx)
{
    AVCodec *codec = NULL;
    AVCodecContext* codec_ctx = NULL;
    AVFrame* frame = NULL;
    AVPacket packet;
    int stream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO,  -1, -1, &codec, 0);
    int res;

    if (stream_idx < 0) {
        printf("Could not find stream.\n");
        return (1);
    }

    if ((codec_ctx = avcodec_alloc_context3(codec)) == NULL) {
        printf("Could not allocate codec context.\n");
        return (1);
    }

    if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[stream_idx]->codecpar) < 0) {
        printf("Could not setup codec context parameters.\n");
        return (1);
    }

    // Explicitly request non planar data.
    codec_ctx->request_sample_fmt = av_get_packed_sample_fmt(codec_ctx->sample_fmt);

    if (avcodec_open2(codec_ctx, codec, NULL) != 0) {
        printf("Could not open codec.\n");
        return (1);
    }

    if ((frame = av_frame_alloc()) == NULL) {
        printf("Could not alloc frame.\n");
        return (1);
    }

    av_init_packet(&packet);

    int fd = open("raw", O_CREAT | O_WRONLY | O_TRUNC);

    // Decode frames.
    while ((res = av_read_frame(format_ctx, &packet)) == 0) {
        // Does the packet belong to the correct stream?
        if (packet.stream_index != stream_idx) {
            av_packet_unref(&packet);
            continue;
        }

        // We have a valid packet => send it to the decoder.
        if ((res = avcodec_send_packet(codec_ctx, &packet)) != 0) {
            printf("Failed to send packet: %d.\n", res);
            break;
        }

        av_packet_unref(&packet);
        res = avcodec_receive_frame(codec_ctx, frame);

        if (res == AVERROR(EAGAIN) || res == AVERROR_EOF)
            break;
        else if (res < 0) {
            printf("Failed to decode packet: %d.\n", res);
            return (1);
        }

        write(fd, frame->extended_data[0], frame->linesize[0]);
    }

    close(fd);
    av_frame_free(&frame);
    avcodec_close(codec_ctx);
    avcodec_free_context(&codec_ctx);
    return (0);
}

int main(int argc, char **argv)
{
    AVFormatContext *av_format_ctx = NULL;

    if (argc != 2) {
        printf("./streamer [file]\n");
        return (1);
    }

    if (avformat_open_input(&av_format_ctx, argv[1], NULL, NULL) != 0) {
        printf("Could not open input file.");
        return (1);
    }

    if (avformat_find_stream_info(av_format_ctx, NULL) != 0) {
        printf("Could not find stream information.");
        return (1);
    }

    decode_raw(av_format_ctx);
    avformat_close_input(&av_format_ctx);
    return (0);
}

我尝试过的

  • 检查字节序,是否正确将原始文件导入Audacity
  • 执行相应的ffmpeg命令ffmpeg -i video.mp4 -f f32le output.raw(我的代码输出AV_SAMPLE_FMT_FLT)来比较两个文件。

我将两个文件都转储并找到了。

// 96 1f 03 3f - 22 03 0c 3f
// Doesn't exist in the output of my program?

5581a0  7c ad 6f bc 96 1f 03 3f 4f 01 25 3e 22 03 0c 3f  |.o....?O.%>"..?   // ffmpeg
5580d0  7c ad 6f bc 4f 01 25 3e 3a d2 89 3e 7c d7 9a 3e  |.o.O.%>:..>|..>   // my implementation

编辑#1

无休止的令人失望的经历之后,AAC音频流在解码后似乎已损坏。但是,ffmpeg的原始PCM输出对于MP4效果很好。

我尝试使用swr_convert对音频帧进行重新采样,但是文档记录太少,因此出现了很多问题。

1 个答案:

答案 0 :(得分:0)

问题

打印有关音频流的信息之后。我注意到AAC(mp4文件的音频编解码器)不支持非平面格式(打包)。

// Explicitly request non planar data.
codec_ctx->request_sample_fmt = av_get_packed_sample_fmt(codec_ctx->sample_fmt);

由于不支持请求的格式,因此与mp3文件不同,mp4文件的音频流被解码为平面音频。

---------
Codec: MP3 (MPEG audio layer 3)
Supported sample formats: fltp, flt        # MP3 support non planar
---------
Stream:              0
Sample Format:    fltp
Sample Rate:     48000
Sample Size:         4
Channels:            2
Planar Output:      yes

---------
Codec: AAC (Advanced Audio Coding)
Supported sample formats: fltp             # AAC doesn't support non planar
---------
Stream:              1
Sample Format:    fltp
Sample Rate:     44100
Sample Size:         4
Channels:            2
Planar Output:      yes

解决方案

为解决此问题,我删除了上面的行以保持流平整。我还必须更改在文件中编写的方式。

由于格式为平面LR, LR, LR且未压缩LL LL RR RR,因此我不得不交替手动编写每个通道。

由于逐字节写入会花费很长时间,因此我编写了一个在将缓冲区写入文件之前先写入缓冲区的函数。

void audio_pack_stream(AVCodecContext* codec_ctx, AVFrame *frame, uint8_t *dst, int *size)
{
    int bytes = av_get_bytes_per_sample(codec_ctx->sample_fmt);
    int actual = 0;

    for (int i = 0; i < frame->nb_samples; i++) {
        for(int j = 0; j < codec_ctx->channels; j++)
            for (int k = 0; k < bytes; k++)
                dst[*size++] = frame->extended_data[j][actual + k];
        actual += bytes;
    }
    return (size);
}

// After avcodec_receive_frame

uint8_t output[4096 * 8];
int size;

audio_pack_stream(codec_ctx, frame, output, &size);
write(fd, output, size);

Fixed Audacity Waveform