今天開(kāi)始擼代碼屡久,首先使用FFmpeg的API抽取一個(gè)MP4文件的音頻數(shù)據(jù)。
IDE
應(yīng)該是第一次在Mac上做C/C++開(kāi)發(fā)爱榔,糾結(jié)過(guò)后選擇使用CLion 開(kāi)發(fā)被环。CLion是 JetBrains下專(zhuān)門(mén)用來(lái)開(kāi)發(fā)C/C++的IDE,已經(jīng)用習(xí)慣了Android studio和IntelliJ IDEA 详幽,所以CLion用起來(lái)還是很順手的蛤售。
在新建一個(gè)C項(xiàng)目后,需要把FFmpeg的庫(kù)導(dǎo)入才能正常運(yùn)行妒潭。我們修改項(xiàng)目的CMakeLists.txt文件悴能。
抽取音頻AAC數(shù)據(jù)
其實(shí)我們要做的主要就是一個(gè)文件的操作,把一個(gè)文件打開(kāi)雳灾,從里面拿出它的一部分?jǐn)?shù)據(jù)漠酿,再把這部分?jǐn)?shù)據(jù)放到另一個(gè)文件中保存。
定義參數(shù)
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
//上下文
AVFormatContext *fmt_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;
//支持各種各樣的輸出文件格式谎亩,MP4炒嘲,F(xiàn)LV,3GP等等
AVOutputFormat *output_fmt = NULL;
//輸入流
AVStream *in_stream = NULL;
//輸出流
AVStream *out_stream = NULL;
//存儲(chǔ)壓縮數(shù)據(jù)
AVPacket packet;
//要拷貝的流
int audio_stream_index = -1;
1.打開(kāi)輸入文件匈庭,提取參數(shù)
//打開(kāi)輸入文件夫凸,關(guān)于輸入文件的所有就保存到fmt_ctx中了
err_code = avformat_open_input(&fmt_ctx, src_fileName, NULL, NULL);
if (err_code < 0) {
av_log(NULL, AV_LOG_ERROR, "cant open file:%s\n", av_err2str(err_code));
return -1;
}
if(fmt_ctx->nb_streams<2){
//流數(shù)小于2,說(shuō)明這個(gè)文件音頻阱持、視頻流這兩條都不能保證夭拌,輸入文件有錯(cuò)誤
av_log(NULL, AV_LOG_ERROR, "輸入文件錯(cuò)誤,流不足2條\n");
exit(1);
}
//拿到文件中音頻流
in_stream = fmt_ctx->streams[1];
//參數(shù)信息
AVCodecParameters *in_codecpar = in_stream->codecpar;
//找到最好的音頻流
audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if(audio_stream_index < 0){
av_log(NULL, AV_LOG_DEBUG, "尋找最好音頻流失敗,請(qǐng)檢查輸入文件鸽扁!\n");
return AVERROR(EINVAL);
}
2.準(zhǔn)備輸出文件蒜绽,輸出流
// 輸出上下文
ofmt_ctx = avformat_alloc_context();
//根據(jù)目標(biāo)文件名生成最適合的輸出容器
output_fmt = av_guess_format(NULL,dst_fileName,NULL);
if(!output_fmt){
av_log(NULL, AV_LOG_DEBUG, "根據(jù)目標(biāo)生成輸出容器失敗桶现!\n");
exit(1);
}
ofmt_ctx->oformat = output_fmt;
//新建輸出流
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if(!out_stream){
av_log(NULL, AV_LOG_DEBUG, "創(chuàng)建輸出流失敹阊拧!\n");
exit(1);
}
3. 數(shù)據(jù)拷貝
3.1 參數(shù)信息
// 將參數(shù)信息拷貝到輸出流中骡和,我們只是抽取音頻流相赁,并不做音頻處理,所以這里只是Copy
if((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0 ){
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,"拷貝編碼參數(shù)失斘坑凇噪生!, %d(%s)\n",
err_code, errors);
}
3.2 初始化AVIOContext
//初始化AVIOContext,文件操作由它完成
if((err_code = avio_open(&ofmt_ctx->pb, dst_fileName, AVIO_FLAG_WRITE)) < 0) {
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "Could not open file %s, %d(%s)\n",
dst_fileName,
err_code,
errors);
exit(1);
}
3.3 開(kāi)始拷貝
//初始化 AVPacket, 我們從文件中讀出的數(shù)據(jù)會(huì)暫存在其中
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
// 寫(xiě)頭部信息
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
av_log(NULL, AV_LOG_DEBUG, "Error occurred when opening output file");
exit(1);
}
//每讀出一幀數(shù)據(jù)
while(av_read_frame(fmt_ctx, &packet) >=0 ){
if(packet.stream_index == audio_stream_index){
//時(shí)間基計(jì)算东囚,音頻pts和dts一致
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.dts = packet.pts;
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
packet.pos = -1;
packet.stream_index = 0;
//將包寫(xiě)到輸出媒體文件
av_interleaved_write_frame(ofmt_ctx, &packet);
//減少引用計(jì)數(shù)跺嗽,避免內(nèi)存泄漏
av_packet_unref(&packet);
}
}
//寫(xiě)尾部信息
av_write_trailer(ofmt_ctx);
//最后別忘了釋放內(nèi)存
avformat_close_input(&fmt_ctx);
avio_close(ofmt_ctx->pb);
執(zhí)行
./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test.aac
抽取視頻數(shù)據(jù)
抽取視頻信息并保存在文件中的流程甚至代碼和上面抽取音頻基本一致。
//拿到文件中音頻流 或者 視頻流页藻,所有流都在streams數(shù)組中
in_stream = fmt_ctx->streams[1];
//找到最好的視頻流
video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
基本上就是一些參數(shù)的改變桨嫁,所有流程和代碼保持不變,就可以把一個(gè)音視頻文件中的視頻數(shù)據(jù)抽取出來(lái)了份帐,mp4璃吧、H264等格式隨便,就是這么簡(jiǎn)單废境。畜挨。。
完整代碼
//
// Created by david on 2019/5/5.
//
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#define ERROR_STR_SIZE 1024
int main(int argc, char *argv[]) {
int err_code;
char errors[ERROR_STR_SIZE];
char *src_filename = NULL;
char *dst_filename = NULL;
int audio_stream_index;
//上下文
AVFormatContext *fmt_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;
//支持各種各樣的輸出文件格式噩凹,MP4巴元,F(xiàn)LV,3GP等等
AVOutputFormat *output_fmt = NULL;
AVStream *in_stream = NULL;
AVStream *out_stream = NULL;
AVPacket pkt;
av_log_set_level(AV_LOG_DEBUG);
if (argc < 3) {
av_log(NULL, AV_LOG_DEBUG, "argc < 3驮宴!\n");
return -1;
}
src_filename = argv[1];
dst_filename = argv[2];
if (src_filename == NULL || dst_filename == NULL) {
av_log(NULL, AV_LOG_DEBUG, "src or dts file is null!\n");
return -1;
}
if ((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0) {
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "打開(kāi)輸入文件失敗: %s, %d(%s)\n",
src_filename,
err_code,
errors);
return -1;
}
if ((err_code = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "failed to find stream info: %s, %d(%s)\n",
src_filename,
err_code,
errors);
return -1;
}
av_dump_format(fmt_ctx, 0, src_filename, 0);
if (fmt_ctx->nb_streams < 2) {
//流數(shù)小于2逮刨,說(shuō)明這個(gè)文件音頻、視頻流這兩條都不能保證堵泽,輸入文件有錯(cuò)誤
av_log(NULL, AV_LOG_ERROR, "輸入文件錯(cuò)誤修己,流不足2條\n");
exit(1);
}
//拿到文件中音頻流
/**只需要修改這里AVMEDIA_TYPE_VIDEO參數(shù)**/
audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO /*AVMEDIA_TYPE_AUDIO*/, -1, -1, NULL, 0);
if (audio_stream_index < 0) {
av_log(NULL, AV_LOG_DEBUG, " 獲取音頻流失敗%s,%s\n",
av_get_media_type_string(AVMEDIA_TYPE_AUDIO),
src_filename);
return AVERROR(EINVAL);
}
in_stream = fmt_ctx->streams[audio_stream_index];
//參數(shù)信息
AVCodecParameters *in_codecpar = in_stream->codecpar;
// 輸出上下文
ofmt_ctx = avformat_alloc_context();
//根據(jù)目標(biāo)文件名生成最適合的輸出容器
output_fmt = av_guess_format(NULL, dst_filename, NULL);
if (!output_fmt) {
av_log(NULL, AV_LOG_DEBUG, "根據(jù)目標(biāo)生成輸出容器失敗迎罗!\n");
exit(1);
}
ofmt_ctx->oformat = output_fmt;
//新建輸出流
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
av_log(NULL, AV_LOG_DEBUG, "創(chuàng)建輸出流失敳欠摺!\n");
exit(1);
}
// 將參數(shù)信息拷貝到輸出流中纹安,我們只是抽取音頻流尤辱,并不做音頻處理砂豌,所以這里只是Copy
if ((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0) {
av_strerror(err_code, errors, ERROR_STR_SIZE);
av_log(NULL, AV_LOG_ERROR,
"拷貝編碼參數(shù)失敗啥刻!, %d(%s)\n",
err_code, errors);
}
out_stream->codecpar->codec_tag = 0;
//初始化AVIOContext,文件操作由它完成
if ((err_code = avio_open(&ofmt_ctx->pb, dst_filename, AVIO_FLAG_WRITE)) < 0) {
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "文件打開(kāi)失敗 %s, %d(%s)\n",
dst_filename,
err_code,
errors);
exit(1);
}
av_dump_format(ofmt_ctx, 0, dst_filename, 1);
//初始化 AVPacket奸鸯, 我們從文件中讀出的數(shù)據(jù)會(huì)暫存在其中
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
// 寫(xiě)頭部信息
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
av_log(NULL, AV_LOG_DEBUG, "寫(xiě)入頭部信息失斶湫Α可帽!");
exit(1);
}
//每讀出一幀數(shù)據(jù)
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == audio_stream_index) {
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
pkt.stream_index = 0;
//將包寫(xiě)到輸出媒體文件
av_interleaved_write_frame(ofmt_ctx, &pkt);
//減少引用計(jì)數(shù),避免內(nèi)存泄漏
av_packet_unref(&pkt);
}
}
//寫(xiě)尾部信息
av_write_trailer(ofmt_ctx);
//最后別忘了釋放內(nèi)存
avformat_close_input(&fmt_ctx);
avio_close(ofmt_ctx->pb);
return 0;
}