抽取音频数据流程
一般步骤为:
- 打开多媒体文件
- 从多媒体文件中找到音频流
- 打开目的文件的上下文
- 为目的文件,创建一个新的音频流
- 设置输出音频参数
- 写多媒体文件头到目的文件
- 从源多媒体文件中读取音/视频数据到目的文件中
- 写多媒体文件尾到文件中
- 将申请的资源释放掉
流程图如下:
从一个MP4文件中,抽取音频数据输出为aac音频文件。
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avio.h>
#include <libavutil/opt.h>
#include <SDL.h>
#include <stdbool.h>
/*抽取音频*/
void getAudio() {
int ret = -1;
int idx = -1;
//1. 处理一些参数;
char* src;
char* dst;
AVFormatContext* pFmtCtx = NULL;
AVFormatContext* oFmtCtx = NULL;
const AVOutputFormat* outFmt = NULL;
AVStream* outStream = NULL;
AVStream* inStream = NULL;
AVPacket pkt;
av_log_set_level(AV_LOG_DEBUG);
src = "F:\\test_data\\crop_jiuzhe_summer.mp4";//输入文件
dst = "F:\\test_data\\crop_jiuzhe_summer.aac";//输出文件
//2. 打开多媒体文件,得到输入文件的封装格式上下文
if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
exit(-1);
}
//3. 从多媒体文件中找到音频流
idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (idx < 0) {
av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream!\n");
goto _ERROR;
}
//4. 创建分配输出封装格式上下文
//oFmtCtx = avformat_alloc_context();
//if (!oFmtCtx) {
// av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");
// goto _ERROR;
//}
//outFmt = av_guess_format(NULL, dst, NULL); // 获取输出文件的基本格式信息。
//oFmtCtx->oformat = outFmt; // 输出目标文件的格式的基本信息赋值给输出格式的上下文。
// 相当于 oFmtCtx = avformat_alloc_context();和outFmt = av_guess_format(NULL, dst, NULL); 这两个步骤
ret = avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "alloc output context fail \n");
}
//5. 创建一个新的音频流,这个流要被写入到目的文件中
outStream = avformat_new_stream(oFmtCtx, NULL);
//6. 设置输出音频参数
inStream = pFmtCtx->streams[idx];//获取输入音频流
avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);// 把输入音频流的编码器参数内容拷贝到目输出音频流编码器中
outStream->codecpar->codec_tag = 0; //ffmpeg根据你的多媒体文件自动适配编解码器
//绑定 将输出封装格式上下文和目的文件进行绑定,绑定之后才可以对于目的文件进行操作
// AVIOContext *pb 是一个输入输出文件上下文
ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);
if (ret < 0) {
av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
//7. 写多媒体文件头到目的文件
ret = avformat_write_header(oFmtCtx, NULL);
if (ret < 0) {
av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
//8. 从源多媒体文件中不停的读音频数据写入到目的文件中
while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 非负值说明读到数据了
if (pkt.stream_index == idx) { // 存在多个流,读取数据所属的流是不是我们想要的流
// 时间基计算 因为输出流的时间基可能和输入的时间基不一样。这样做的目的是让输出流的时间戳与输入流的时间戳保持同步
//pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
//pkt.dts = pkt.pts; // 音频的pts和dts一般是相等的 因为没有B帧。
// 设置音频帧时长
//pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);
pkt.stream_index = 0; // 因为我们只有一路音频,流的索引是从0开始递增的,不按照递增顺序来,会报错(无效的流索引),所以设置音频流的索引设置为0
pkt.pos = -1; // pos字段可以用于表示该数据包在文件中的偏移量。pos字段的值为-1时,表示该数据包的偏移量未知。让它自己进行计算
av_interleaved_write_frame(oFmtCtx, &pkt);// 将音频数据写入目标文件中
av_packet_unref(&pkt);// 释放packet下次再用。
}
}
//9. 写多媒体文件尾到文件中
av_write_trailer(oFmtCtx);
//10. 将申请的资源释放掉
_ERROR:
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
pFmtCtx = NULL;
}
if (oFmtCtx->pb) {
avio_close(oFmtCtx->pb);
}
if (oFmtCtx) {
avformat_free_context(oFmtCtx);
oFmtCtx = NULL;
}
printf("hello, world!\n");
}