音频的解码和视频的类似,查找音频流,查找解码器,打开解码器实例,解码流程也是一样的,把对应的视频解码器实例替换成音频解码器实例即可。

int audio_index = -1;
    AVCodecContext* audio_decode_ctx = nullptr;
    audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    AVStream * audio_stream = nullptr;
    if (audio_index >= 0) {
         audio_stream = fmt_ctx->streams[audio_index];
        enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
        // 查找音频解码器
        AVCodec *audio_codec = (AVCodec*) avcodec_find_decoder(audio_codec_id);
        if (!audio_codec) {
            return -1;
        }
        audio_decode_ctx = avcodec_alloc_context3(audio_codec); // 分配解码器的实例
        if (!audio_decode_ctx) {
            return -1;
        }
        // 把音频流中的编解码参数复制给解码器的实例
        avcodec_parameters_to_context(audio_decode_ctx, audio_stream->codecpar);
        ret = avcodec_open2(audio_decode_ctx, audio_codec, NULL); // 打开解码器的实例
        if (ret < 0) {
            return -1;
        }
    }

和视频帧类似,音频解码也需要进行格式转换,一方面不同视频的音频格式不同,转换成统一格式方便后续处理,另外一方面,有的格式适合存储,有的格式适合编解码。
与视频格式转换类似的是,首先需要先创建一个格式转换的实例,不同的是,音频格式转换需要先设置参数并分配一个格式转换实例,再调用 swr_init 进行初始化。

// 音频重采样
    SwrContext *swr_ctx = nullptr;
    AVChannelLayout audio_out_ch_layout = audio_decode_ctx->ch_layout;                          // 输出的声道布局
    const int audio_out_sample_rate = audio_decode_ctx->sample_rate;                            // 输出采样率
    const AVSampleFormat audio_out_sample_fmt = AV_SAMPLE_FMT_S16;                              // 输出采样格式
    ret = swr_alloc_set_opts2(&swr_ctx,
                                  &audio_out_ch_layout, // 输出的声道布局
                                  audio_out_sample_fmt, // 输出的采样格式
                                  audio_out_sample_rate, // 输出的采样频率
                                  &audio_decode_ctx->ch_layout, // 输入的声道布局
                                  audio_decode_ctx->sample_fmt, // 输入的采样格式
                                  audio_decode_ctx->sample_rate, // 输入的采样频率
                                  0, NULL);
    if (ret < 0) {
        return -1;
    }
    ret = swr_init(swr_ctx);
    if (ret < 0) {
        return -1;
    }

后续的流程也基本类似,创建一个 AVFrame 对象(用于接收转换后的帧),为其分配空间,调用格式转换函数。

AVFrame *converted_frame = av_frame_alloc();
                converted_frame->format = audio_out_sample_fmt;
                converted_frame->ch_layout = audio_decode_ctx->ch_layout;
                converted_frame->sample_rate = audio_decode_ctx->sample_rate;
                int out_nb_samples = swr_get_out_samples(swr_ctx, frame->nb_samples);
                converted_frame->nb_samples = out_nb_samples;

                // 分配转换后帧的缓冲区
                if (av_frame_get_buffer(converted_frame, 0) < 0) {
                    break;
                }

                // 转换音频格式
                if (swr_convert(swr_ctx, converted_frame->data, out_nb_samples,
                                (const uint8_t **)frame->data, frame->nb_samples) < 0) {
                    break;
                }

这里是完整代码

#include <string>
#include <vector>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/channel_layout.h"
}

using namespace std;
// 保存成 wav
int save_to_wav(std::vector<AVFrame*> frames, const char *output_path) {

    if (frames.empty()) {
        return -1;
    }
    AVFormatContext *fmt_ctx = NULL;
    if (avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, output_path) < 0) {
        return -1;
    }

    const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
    if (!codec) {
        avformat_free_context(fmt_ctx);
        return -1;
    }
    AVStream *stream = avformat_new_stream(fmt_ctx, codec);
    if (!stream) {
        avformat_free_context(fmt_ctx);
        return -1;
    }

    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        avformat_free_context(fmt_ctx);
        return -1;
    }

    AVFrame *first_frame = frames[0];
    codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;    // WAV常用16位PCM
    codec_ctx->sample_rate  = first_frame->sample_rate;
    codec_ctx->ch_layout = first_frame->ch_layout;
    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;

    // 复制编码器参数到流
    if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
        avcodec_free_context(&codec_ctx);
        avformat_free_context(fmt_ctx);
        return -1;
    }

    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        avcodec_free_context(&codec_ctx);
        avformat_free_context(fmt_ctx);
        return -1;
    }

    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&fmt_ctx->pb, output_path, AVIO_FLAG_WRITE) < 0) {
            avcodec_free_context(&codec_ctx);
            avformat_free_context(fmt_ctx);
            return -1;
        }
    }
    
    if (avformat_write_header(fmt_ctx, NULL) < 0) {
        avio_closep(&fmt_ctx->pb);
        avcodec_free_context(&codec_ctx);
        avformat_free_context(fmt_ctx);
        return -1;
    }

    AVPacket *packet = av_packet_alloc();
    for (int i = 0; i < frames.size(); i++) {
        AVFrame *frame = frames[i];
        if (!frame) continue;

        // 发送帧到编码器
        if (avcodec_send_frame(codec_ctx, frame) < 0) {
            break;
        }

        // 接收编码后的数据包并写入文件
        while (avcodec_receive_packet(codec_ctx, packet) == 0) {
            packet->stream_index = stream->index;
            av_write_frame(fmt_ctx, packet);
            av_packet_unref(packet);
        }

        av_frame_free(&frame);
    }

    avcodec_send_frame(codec_ctx, NULL);
    while (avcodec_receive_packet(codec_ctx, packet) == 0) {
        packet->stream_index = stream->index;
        av_write_frame(fmt_ctx, packet);
        av_packet_unref(packet);
    }

    av_write_trailer(fmt_ctx);

    av_packet_free(&packet);
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&fmt_ctx->pb);
    }
    avcodec_free_context(&codec_ctx);
    avformat_free_context(fmt_ctx);

    return 0;
}

int main() {
    AVFormatContext *fmt_ctx = NULL;
    const char *filename = "test.mp4";
    // 打开输入文件
    int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if (ret < 0) {
        return ret;
    }
    // 查找流信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        // 没有找到视频流信息
        return ret;
    }

    // 查找视频流
    int video_index = -1;
    video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

    if (video_index == -1) {
        return -1;
    }

    AVStream *video_stream = fmt_ctx->streams[video_index];
    // 查找视频解码器
    enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
    const AVCodec *video_codec = avcodec_find_decoder(video_codec_id);
    if (!video_codec) {
        return -1;
    }


    AVCodecContext *video_decode_ctx = avcodec_alloc_context3(video_codec);
    avcodec_parameters_to_context(video_decode_ctx, video_stream->codecpar);
    ret = avcodec_open2(video_decode_ctx, NULL, NULL);
    if (ret < 0) {
        return ret;
    }

    std::vector<AVFrame*> frames;
    int audio_index = -1;
    AVCodecContext* audio_decode_ctx = nullptr;
    audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    AVStream * audio_stream = nullptr;
    if (audio_index >= 0) {
         audio_stream = fmt_ctx->streams[audio_index];
        enum AVCodecID audio_codec_id = audio_stream->codecpar->codec_id;
        // 查找音频解码器
        AVCodec *audio_codec = (AVCodec*) avcodec_find_decoder(audio_codec_id);
        if (!audio_codec) {
            return -1;
        }
        audio_decode_ctx = avcodec_alloc_context3(audio_codec); // 分配解码器的实例
        if (!audio_decode_ctx) {
            return -1;
        }
        // 把音频流中的编解码参数复制给解码器的实例
        avcodec_parameters_to_context(audio_decode_ctx, audio_stream->codecpar);
        ret = avcodec_open2(audio_decode_ctx, audio_codec, NULL); // 打开解码器的实例
        if (ret < 0) {
            return -1;
        }
    }

    AVPacket* packet = av_packet_alloc();
    AVFrame*  frame  = av_frame_alloc();

    // 格式转换
    AVFrame* rgb_frame = av_frame_alloc();
    if (!rgb_frame) {
        return -1;
    }
    rgb_frame->format = AV_PIX_FMT_RGB24;
    rgb_frame->width = video_stream->codecpar->width;
    rgb_frame->height = video_stream->codecpar->height;

    ret = av_frame_get_buffer(rgb_frame, 0);
    if (ret < 0) {
        av_frame_free(&rgb_frame);
        return -1;
    }

    // 视频格式转换上下文
    SwsContext* sws_ctx = sws_getContext(
        video_stream->codecpar->width, video_stream->codecpar->width, (AVPixelFormat)video_stream->codecpar->format,
        rgb_frame->width, rgb_frame->height, AV_PIX_FMT_RGB24,
        SWS_BILINEAR, nullptr, nullptr, nullptr
        );
    if (!sws_ctx) {
        av_frame_free(&rgb_frame);
        return -1;
    }

    // 音频重采样
    SwrContext *swr_ctx = nullptr;
    AVChannelLayout audio_out_ch_layout = audio_decode_ctx->ch_layout;                          // 输出的声道布局
    const int audio_out_sample_rate = audio_decode_ctx->sample_rate;                            // 输出采样率
    const AVSampleFormat audio_out_sample_fmt = AV_SAMPLE_FMT_S16;                              // 输出采样格式
    ret = swr_alloc_set_opts2(&swr_ctx,
                                  &audio_out_ch_layout, // 输出的声道布局
                                  audio_out_sample_fmt, // 输出的采样格式
                                  audio_out_sample_rate, // 输出的采样频率
                                  &audio_decode_ctx->ch_layout, // 输入的声道布局
                                  audio_decode_ctx->sample_fmt, // 输入的采样格式
                                  audio_decode_ctx->sample_rate, // 输入的采样频率
                                  0, NULL);
    if (ret < 0) {
        return -1;
    }
    ret = swr_init(swr_ctx);
    if (ret < 0) {
        return -1;
    }

    // 读取数据 这里是把视频包读到 packet , 函数名称则是历史遗留问题
    while (av_read_frame(fmt_ctx, packet) >= 0) { // 轮询数据包
        if (packet->stream_index == video_index) {
            ret = avcodec_send_packet(video_decode_ctx, packet);
            while ( ret >= 0) {
                av_frame_unref(frame);
                ret = avcodec_receive_frame(video_decode_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    return -1;
                }

                ret = sws_scale(
                    sws_ctx,
                    frame->data, frame->linesize,
                    0, frame->height,
                    rgb_frame->data, rgb_frame->linesize
                    );
            }
        } else if (packet->stream_index == audio_index) {
            ret = avcodec_send_packet(audio_decode_ctx, packet);
            while ( ret >= 0) {
                av_frame_unref(frame);
                ret = avcodec_receive_frame(audio_decode_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    return -1;
                }

                AVFrame *converted_frame = av_frame_alloc();
                converted_frame->format = AV_SAMPLE_FMT_S16;
                converted_frame->ch_layout = audio_decode_ctx->ch_layout;
                converted_frame->sample_rate = audio_decode_ctx->sample_rate;
                int out_nb_samples = swr_get_out_samples(swr_ctx, frame->nb_samples);
                converted_frame->nb_samples = out_nb_samples;

                // 分配转换后帧的缓冲区
                if (av_frame_get_buffer(converted_frame, 0) < 0) {
                    break;
                }

                // 转换音频格式
                if (swr_convert(swr_ctx, converted_frame->data, out_nb_samples,
                                (const uint8_t **)frame->data, frame->nb_samples) < 0) {
                    break;
                }

                frames.emplace_back(converted_frame);

            }
        }
        // 清空 packet 内容
        av_packet_unref(packet);
    }

    save_to_wav(frames, "test.wav");
    // 释放内存
    sws_freeContext(sws_ctx);
    avformat_close_input(&fmt_ctx);
    avcodec_free_context(&video_decode_ctx);
    avcodec_free_context(&audio_decode_ctx);
    av_packet_free(&packet);
    av_frame_free(&frame);
    av_frame_free(&rgb_frame);
    return 0;
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐