ffmpeg 入门(五、音频帧解码及重采样)
·
音频的解码和视频的类似,查找音频流,查找解码器,打开解码器实例,解码流程也是一样的,把对应的视频解码器实例替换成音频解码器实例即可。
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;
}
更多推荐
所有评论(0)