//導入頭文件
//核心庫
#include "libavcodec/avcodec.h"
//封裝格式處理庫
#include "libavformat/avformat.h"
//工具庫
#include "libavutil/imgutils.h"
//視頻像素數據格式庫
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
.h文件的類方法
//視頻解碼
+(void) ffmepgVideoDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;
//音頻解碼
+(void)ffmpegAudioDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;
//FFmpeg視頻編碼
+(void)ffmpegVideoEncode:(NSString*)filePath outFilePath:(NSString*)outFilePath;
//音頻編碼
+(void)ffmpegAudioEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;
.m的實現
//inFilePath:輸入文件->mp4、mov等等->封裝格式
//outFilePath:輸出文件->YUV文件->視頻像素數據格式
+(void) ffmepgVideoDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{
//第一步:組冊組件
av_register_all();
//第二步:打開封裝格式->打開文件
//參數一:封裝格式上下文
//作用:保存整個視頻信息(解碼器、編碼器等等...)
//信息:碼率、幀率等...
AVFormatContext* avformat_context = avformat_alloc_context();
//參數二:視頻路徑
const char *url = [inFilePath UTF8String];
//在我們iOS裏面
//NSString* path = @"/user/dream/test.mov";
//const char *url = [path UTF8String]
//參數三:指定輸入的格式
//參數四:設置默認參數
int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
if (avformat_open_input_result != 0){
//安卓平臺下log
NSLog(@"打開文件失敗");
//iOS平臺下log
//NSLog("打開文件失敗");
//不同的平臺替換不同平臺log日誌
return;
}
//第三步:查找視頻流->拿到視頻信息
//參數一:封裝格式上下文
//參數二:指定默認配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
if (avformat_find_stream_info_result < 0){
NSLog(@"查找失敗");
return;
}
//第四步:查找視頻解碼器
//1、查找視頻流索引位置
int av_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//判斷流類型:視頻流、音頻流、字母流等等...
if (avformat_context->streams[i]-> codec->codec_type == AVMEDIA_TYPE_VIDEO){
av_stream_index = i;
break;
}
}
//2、根據視頻流索引,獲取解碼器上下文
AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]-> codec;
//3、根據解碼器上下文,獲得解碼器ID,然後查找解碼器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打開解碼器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0){
NSLog(@"打開解碼器失敗");
return;
}
//測試一下
//打印信息
NSLog(@"解碼器名稱:%s", avcodec->name);
//第六步:讀取視頻壓縮數據->循環讀取
//1、分析av_read_frame參數
//參數一:封裝格式上下文
//參數二:一幀壓縮數據 = 一張圖片
//av_read_frame()
//結構體大小計算:字節對齊原則
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用於解碼操作)
//開闢一塊內存空間
AVFrame* avframe_in = av_frame_alloc();
int decode_result = 0;
//4、注意:在這裏我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式
//參數一:源文件->原始視頻像素數據格式寬
//參數二:源文件->原始視頻像素數據格式高
//參數三:源文件->原始視頻像素數據格式類型
//參數四:目標文件->目標視頻像素數據格式寬
//參數五:目標文件->目標視頻像素數據格式高
//參數六:目標文件->目標視頻像素數據格式類型
struct SwsContext *swscontext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,
NULL,
NULL);
//創建一個yuv420視頻像素數據格式緩衝區(一幀數據)
AVFrame* avframe_yuv420p = av_frame_alloc();
//給緩衝區設置類型->yuv420類型
//得到YUV420P緩衝區大小
//參數一:視頻像素數據格式類型->YUV420P格式
//參數二:一幀視頻像素數據寬 = 視頻寬
//參數三:一幀視頻像素數據高 = 視頻高
//參數四:字節對齊方式->默認是1
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
//開闢一塊內存空間
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//向avframe_yuv420p->填充數據
//參數一:目標->填充數據(avframe_yuv420p)
//參數二:目標->每一行大小
//參數三:原始數據
//參數四:目標->格式類型
//參數五:寬
//參數六:高
//參數七:字節對齊方式
av_image_fill_arrays(avframe_yuv420p->data,
avframe_yuv420p->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
int y_size, u_size, v_size;
//5.2 將yuv420p數據寫入.yuv文件中
//打開寫入文件
const char *outfile = [outFilePath UTF8String];
FILE* file_yuv420p = fopen(outfile, "wb+");
if (file_yuv420p == NULL){
NSLog(@"輸出文件打開失敗");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, packet) >= 0){
// >=:讀取到了
// <0:讀取錯誤或者讀取完畢
//2、是否是我們的視頻流
if (packet->stream_index == av_stream_index){
//第七步:解碼
//學習一下C基礎,結構體
//3、解碼一幀壓縮數據->得到視頻像素數據->yuv格式
//採用新的API
//3.1 發送一幀視頻壓縮數據
avcodec_send_packet(avcodec_context, packet);
//3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用於解碼操作)
decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
if (decode_result == 0){
//解碼成功
//4、注意:在這裏我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式
//視頻像素數據格式很多種類型: yuv420P、yuv422p、yuv444p等等...
//保證:我的解碼後的視頻像素數據格式統一爲yuv420P->通用的格式
//進行類型轉換: 將解碼出來的視頻像素點數據格式->統一轉類型爲yuv420P
//sws_scale作用:進行類型轉換的
//參數一:視頻像素數據格式上下文
//參數二:原來的視頻像素數據格式->輸入數據
//參數三:原來的視頻像素數據格式->輸入畫面每一行大小
//參數四:原來的視頻像素數據格式->輸入畫面每一行開始位置(填寫:0->表示從原點開始讀取)
//參數五:原來的視頻像素數據格式->輸入數據行數
//參數六:轉換類型後視頻像素數據格式->輸出數據
//參數七:轉換類型後視頻像素數據格式->輸出畫面每一行大小
sws_scale(swscontext,
(const uint8_t *const *)avframe_in->data,
avframe_in->linesize,
0,
avcodec_context->height,
avframe_yuv420p->data,
avframe_yuv420p->linesize);
//方式一:直接顯示視頻上面去
//方式二:寫入yuv文件格式
//5、將yuv420p數據寫入.yuv文件中
//5.1 計算YUV大小
//分析一下原理?
//Y表示:亮度
//UV表示:色度
//有規律
//YUV420P格式規範一:Y結構表示一個像素(一個像素對應一個Y)
//YUV420P格式規範二:4個像素點對應一個(U和V: 4Y = U = V)
y_size = avcodec_context->width * avcodec_context->height;
u_size = y_size / 4;
v_size = y_size / 4;
//5.2 寫入.yuv文件
//首先->Y數據
fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
//其次->U數據
fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
//再其次->V數據
fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
current_index++;
NSLog(@"當前解碼第%d幀", current_index);
}
}
}
//第八步:釋放內存資源,關閉解碼器
av_packet_free(&packet);
fclose(file_yuv420p);
av_frame_free(&avframe_in);
av_frame_free(&avframe_yuv420p);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
//音頻解碼
+(void)ffmpegAudioDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{
//第一步:組冊組件->解碼器、編碼器等等…
//視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器等等…
av_register_all();
//第二步:打開封裝格式文件(解封裝)
//參數一:封裝格式上下文
AVFormatContext *avformat_context = avformat_alloc_context();
//參數二:視頻路徑
const char *cinFilePath = [inFilePath UTF8String];
//參數三:指定輸入的格式
//參數四:設置默認參數
if (avformat_open_input(&avformat_context, cinFilePath, NULL, NULL) != 0) {
NSLog(@"打開文件失敗");
return;
}
//第三步:查找音頻流(視頻流、字母流等…)信息
if (avformat_find_stream_info(avformat_context, NULL) < 0) {
NSLog(@"查找失敗");
return;
}
//第四步:查找音頻解碼器
//1、查找音頻流索引位置
int av_audio_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//判斷流類型:視頻流、音頻流、字母流等等...
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
av_audio_stream_index = i;
break;
}
}
//2、根據視頻流索引,獲取解碼器上下文
AVCodecContext *avcodec_context = avformat_context->streams[av_audio_stream_index]->codec;
//3、根據音頻解碼器上下文,獲得解碼器ID,然後查找音頻解碼器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打開音頻解碼器
if (avcodec_open2(avcodec_context, avcodec, NULL) != 0) {
NSLog(@"打開解碼器失敗");
return;
}
//打印信息
NSLog(@"解碼器名稱:%s", avcodec->name);
//第六步:循環讀取每一幀音頻壓縮數據
//參數一:封裝格式上下文呢
//參數二:音頻壓縮數據(一幀)
//返回值:>=0表示讀取成功,<0表示失敗或者解碼完成(讀取完畢)
//準備一幀音頻壓縮數據
AVPacket *avPacket = (AVPacket *) av_malloc(sizeof(AVPacket));
//準備一幀音頻採樣數據
AVFrame *avFrame = av_frame_alloc();
//3、類型轉換->統一轉換爲pcm格式->swr_convert()
//初始化音頻採樣數據上下文
//3.1:開闢一塊內存空間
SwrContext *swrContext = swr_alloc();
//3.2:設置默認配置
//參數一:音頻採樣數據上下文
//參數二:輸出聲道佈局(立體聲、環繞聲...)
//參數三:輸出採樣精度(編碼)
//參數四:輸出採樣率
//參數五:輸入聲道佈局
int64_t in_ch_layout = av_get_default_channel_layout(avcodec_context->channels);
//參數六:輸入採樣精度
//參數七:輸入採樣率
//參數八:日誌統計開始位置
//參數九:日誌上下文
swr_alloc_set_opts(swrContext,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
avcodec_context->sample_rate,
in_ch_layout,
avcodec_context->sample_fmt,
avcodec_context->sample_rate,
0,
NULL);
//3.3:初始化上下文
swr_init(swrContext);
//3.4:統一輸出音頻採樣數據格式->pcm
int MAX_AUDIO_SIZE = 44100 * 2;
uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_SIZE);
//4、獲取緩衝區實際大小
int out_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
//5.1 打開文件
const char *outfile = [outFilePath UTF8String];
FILE* file_pcm = fopen(outfile, "wb+");
if (file_pcm == NULL){
NSLog(@"輸出文件打開失敗");
return;
}
int current_index = 0;
while (av_read_frame(avformat_context, avPacket) >= 0) {
//判定這一幀數據是否音頻流(視頻流、音頻流、字母流等等...)
//1、音頻解碼->判定流類型
if (avPacket->stream_index == av_audio_stream_index) {
//音頻流->處理
//2、音頻解碼->開始解碼
//2.1 發送數據包->一幀音頻壓縮數據->acc格式、mp3格式
avcodec_send_packet(avcodec_context, avPacket);
//2.2 解碼數據包->解碼->一幀音頻採樣數據->pcm格式
int ret = avcodec_receive_frame(avcodec_context, avFrame);
if (ret == 0) {
//表示解碼成功,否則失敗
//3、類型轉換->統一轉換爲pcm格式->swr_convert()
//爲什麼呢?因爲解碼之後的音頻採樣數據格式->有很多種類型->保證格式一致
//參數一:音頻採樣數據上下文
//參數二:輸出音頻採樣數據
//參數三:輸出音頻採樣數據大小
//參數四:輸入音頻採樣數據
//參數五:輸入音頻採樣數據大小
swr_convert(swrContext,
&out_buffer,
MAX_AUDIO_SIZE,
(const uint8_t **) avFrame->data,
avFrame->nb_samples);
//4、獲取緩衝區實際大小
//參數一:行大小
//參數二:輸出聲道數量(單聲道、雙聲道)
//參數三:輸入大小
//參數四:輸出音頻採樣數據格式
//參數五:字節對齊方式->默認是1
int buffer_size = av_samples_get_buffer_size(NULL,
out_nb_channels,
avFrame->nb_samples,
avcodec_context->sample_fmt,
1);
//5、寫入文件
//5.1 打開文件
//5.2 寫入文件
fwrite(out_buffer, 1, buffer_size, file_pcm);
current_index++;
NSLog(@"當前解碼到了第%d幀", current_index);
}
}
}
//第八步:釋放資源(內存)->關閉解碼器
av_packet_free(&avPacket);
fclose(file_pcm);
av_frame_free(&avFrame);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
+(void)ffmpegVideoEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{
//第一步:註冊組件->編碼器、解碼器等等…
av_register_all();
//第二步:初始化封裝格式上下文->視頻編碼->處理爲視頻壓縮數據格式
AVFormatContext *avformat_context = avformat_alloc_context();
//注意事項:FFmepg程序推測輸出文件類型->視頻壓縮數據格式類型
const char *coutFilePath = [outFilePath UTF8String];
//得到視頻壓縮數據格式類型(h264、h265、mpeg2等等...)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
//指定類型
avformat_context->oformat = avoutput_format;
//第三步:打開輸出文件
//參數一:輸出流
//參數二:輸出文件
//參數三:權限->輸出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
NSLog(@"打開輸出文件失敗");
return;
}
//第四步:創建輸出碼流->創建了一塊內存空間->並不知道他是什麼類型流->希望他是視頻流
AVStream *av_video_stream = avformat_new_stream(avformat_context, NULL);
//第五步:查找視頻編碼器
//1、獲取編碼器上下文
AVCodecContext *avcodec_context = av_video_stream->codec;
//2、設置編解碼器上下文參數->必需設置->不可少
//目標:設置爲是一個視頻編碼器上下文->指定的是視頻編碼器
//上下文種類:視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器
//2.1 設置視頻編碼器ID
avcodec_context->codec_id = avoutput_format->video_codec;
//2.2 設置編碼器類型->視頻編碼器
//視頻編碼器->AVMEDIA_TYPE_VIDEO
//音頻編碼器->AVMEDIA_TYPE_AUDIO
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
//2.3 設置讀取像素數據格式->編碼的是像素數據格式->視頻像素數據格式->YUV420P(YUV422P、YUV444P等等...)
//注意:這個類型是根據你解碼的時候指定的解碼的視頻像素數據格式類型
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
//2.4 設置視頻寬高->視頻尺寸
avcodec_context->width = 640;
avcodec_context->height = 352;
//2.5 設置幀率->表示每秒25幀
//視頻信息->幀率 : 25.000 fps
//f表示:幀數
//ps表示:時間(單位:每秒)
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;
//2.6 設置碼率
//2.6.1 什麼是碼率?
//含義:每秒傳送的比特(bit)數單位爲 bps(Bit Per Second),比特率越高,傳送數據速度越快。
//單位:bps,"b"表示數據量,"ps"表示每秒
//目的:視頻處理->視頻碼率
//2.6.2 什麼是視頻碼率?
//含義:視頻碼率就是數據傳輸時單位時間傳送的數據位數,一般我們用的單位是kbps即千位每秒
//視頻碼率計算如下?
//基本的算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)
//例如:Test.mov時間 = 24,文件大小(視頻+音頻) = 1.73MB
//視頻大小 = 1.34MB(文件佔比:77%) = 1.34MB * 1024 * 1024 * 8 = 字節大小 = 468365字節 = 468Kbps
//音頻大小 = 376KB(文件佔比:21%)
//計算出來值->碼率 : 468Kbps->表示1000,b表示位(bit->位)
//總結:碼率越大,視頻越大
avcodec_context->bit_rate = 468000;
//2.7 設置GOP->影響到視頻質量問題->畫面組->一組連續畫面
//MPEG格式畫面類型:3種類型->分爲->I幀、P幀、B幀
//I幀->內部編碼幀->原始幀(原始視頻數據)
// 完整畫面->關鍵幀(必需的有,如果沒有I,那麼你無法進行編碼,解碼)
// 視頻第1幀->視頻序列中的第一個幀始終都是I幀,因爲它是關鍵幀
//P幀->向前預測幀->預測前面的一幀類型,處理數據(前面->I幀、B幀)
// P幀數據->根據前面的一幀數據->進行處理->得到了P幀
//B幀->前後預測幀(雙向預測幀)->前面一幀和後面一幀
// B幀壓縮率高,但是對解碼性能要求較高。
//總結:I只需要考慮自己 = 1幀,P幀考慮自己+前面一幀 = 2幀,B幀考慮自己+前後幀 = 3幀
// 說白了->P幀和B幀是對I幀壓縮
//每250幀,插入1個I幀,I幀越少,視頻越小->默認值->視頻不一樣
avcodec_context->gop_size = 250;
//2.8 設置量化參數->數學算法(高級算法)->不講解了
//總結:量化係數越小,視頻越是清晰
//一般情況下都是默認值,最小量化係數默認值是10,最大量化係數默認值是51
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;
//2.9 設置b幀最大值->設置不需要B幀
avcodec_context->max_b_frames = 0;
//第二點:查找編碼器->h264
//找不到編碼器->h264
//重要原因是因爲:編譯庫沒有依賴x264庫(默認情況下FFmpeg沒有編譯進行h264庫)
//第一步:編譯h264庫
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if (avcodec == NULL) {
NSLog(@"找不到編碼器");
return;
}
NSLog(@"編碼器名稱爲:%s", avcodec->name);
//第六步:打開h264編碼器
//缺少優化步驟?
//編碼延時問題
//編碼選項->編碼設置
AVDictionary *param = 0;
if (avcodec_context->codec_id == AV_CODEC_ID_H264) {
//需要查看x264源碼->x264.c文件
//第一個值:預備參數
//key: preset
//value: slow->慢
//value: superfast->超快
av_dict_set(¶m, "preset", "slow", 0);
//第二個值:調優
//key: tune->調優
//value: zerolatency->零延遲
av_dict_set(¶m, "tune", "zerolatency", 0);
}
if (avcodec_open2(avcodec_context, avcodec, ¶m) < 0) {
NSLog(@"打開編碼器失敗");
return;
}
//第七步:寫入文件頭信息
avformat_write_header(avformat_context, NULL);
//第8步:循環編碼yuv文件->視頻像素數據(yuv格式)->編碼->視頻壓縮數據(h264格式)
//8.1 定義一個緩衝區
//作用:緩存一幀視頻像素數據
//8.1.1 獲取緩衝區大小
int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
//8.1.2 創建一個緩衝區
int y_size = avcodec_context->width * avcodec_context->height;
uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);
//8.1.3 打開輸入文件
const char *cinFilePath = [inFilePath UTF8String];
FILE *in_file = fopen(cinFilePath, "rb");
if (in_file == NULL) {
NSLog(@"文件不存在");
return;
}
//8.2.1 開闢一塊內存空間->av_frame_alloc
//開闢了一塊內存空間
AVFrame *av_frame = av_frame_alloc();
//8.2.2 設置緩衝區和AVFrame類型保持一直->填充數據
av_image_fill_arrays(av_frame->data,
av_frame->linesize,
out_buffer,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
int i = 0;
//9.2 接收一幀視頻像素數據->編碼爲->視頻壓縮數據格式
AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);
int result = 0;
int current_frame_index = 1;
while (true) {
//8.1 從yuv文件裏面讀取緩衝區
//讀取大小:y_size * 3 / 2
if (fread(out_buffer, 1, y_size * 3 / 2, in_file) <= 0) {
NSLog(@"讀取完畢...");
break;
} else if (feof(in_file)) {
break;
}
//8.2 將緩衝區數據->轉成AVFrame類型
//給AVFrame填充數據
//8.2.3 void * restrict->->轉成->AVFrame->ffmpeg數據類型
//Y值
av_frame->data[0] = out_buffer;
//U值
av_frame->data[1] = out_buffer + y_size;
//V值
av_frame->data[2] = out_buffer + y_size * 5 / 4;
av_frame->pts = i;
//注意時間戳
i++;
//總結:這樣一來我們的AVFrame就有數據了
//第9步:視頻編碼處理
//9.1 發送一幀視頻像素數據
avcodec_send_frame(avcodec_context, av_frame);
//9.2 接收一幀視頻像素數據->編碼爲->視頻壓縮數據格式
result = avcodec_receive_packet(avcodec_context, av_packet);
//9.3 判定是否編碼成功
if (result == 0) {
//編碼成功
//第10步:將視頻壓縮數據->寫入到輸出文件中->outFilePath
av_packet->stream_index = av_video_stream->index;
result = av_write_frame(avformat_context, av_packet);
NSLog(@"當前是第%d幀", current_frame_index);
current_frame_index++;
//是否輸出成功
if (result < 0) {
NSLog(@"輸出一幀數據失敗");
return;
}
}
}
//第11步:寫入剩餘幀數據->可能沒有
flush_encoder(avformat_context, 0);
//第12步:寫入文件尾部信息
av_write_trailer(avformat_context);
//第13步:釋放內存
avcodec_close(avcodec_context);
av_free(av_frame);
av_free(out_buffer);
av_packet_free(&av_packet);
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
fclose(in_file);
}
+(void)ffmpegAudioEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{
//第一步:註冊組件->音頻編碼器等等…
av_register_all();
//第二步:初始化封裝格式上下文->視頻編碼->處理爲音頻壓縮數據格式
AVFormatContext *avformat_context = avformat_alloc_context();
//注意事項:FFmepg程序推測輸出文件類型->音頻壓縮數據格式類型->aac格式
const char *coutFilePath = [outFilePath UTF8String];
//得到音頻壓縮數據格式類型(aac、mp3等...)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
//指定類型
avformat_context->oformat = avoutput_format;
//第三步:打開輸出文件
//參數一:輸出流
//參數二:輸出文件
//參數三:權限->輸出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
NSLog(@"打開輸出文件失敗");
return;
}
//第四步:創建輸出碼流->創建了一塊內存空間->並不知道他是什麼類型流->希望他是視頻流
AVStream *audio_st = avformat_new_stream(avformat_context, NULL);
//第五步:查找音頻編碼器
//1、獲取編碼器上下文
AVCodecContext *avcodec_context = audio_st->codec;
//2、設置編解碼器上下文參數->必需設置->不可少
//目標:設置爲是一個音頻編碼器上下文->指定的是音頻編碼器
//上下文種類:音頻解碼器、音頻編碼器
//2.1 設置音頻編碼器ID
avcodec_context->codec_id = avoutput_format->audio_codec;
//2.2 設置編碼器類型->音頻編碼器
//視頻編碼器->AVMEDIA_TYPE_VIDEO
//音頻編碼器->AVMEDIA_TYPE_AUDIO
avcodec_context->codec_type = AVMEDIA_TYPE_AUDIO;
//2.3 設置讀取音頻採樣數據格式->編碼的是音頻採樣數據格式->音頻採樣數據格式->pcm格式
//注意:這個類型是根據你解碼的時候指定的解碼的音頻採樣數據格式類型
avcodec_context->sample_fmt = AV_SAMPLE_FMT_S16;
//設置採樣率
avcodec_context->sample_rate = 44100;
//立體聲
avcodec_context->channel_layout = AV_CH_LAYOUT_STEREO;
//聲道數量
int channels = av_get_channel_layout_nb_channels(avcodec_context->channel_layout);
avcodec_context->channels = channels;
//設置碼率
//基本的算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)
avcodec_context->bit_rate = 128000;
//第二點:查找音頻編碼器->aac
// AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
AVCodec *avcodec = avcodec_find_encoder_by_name("libfdk_aac");
if (avcodec == NULL) {
NSLog(@"找不到音頻編碼器");
return;
}
//第六步:打開aac編碼器
if (avcodec_open2(avcodec_context, avcodec, NULL) < 0) {
NSLog(@"打開音頻編碼器失敗");
return;
}
//第七步:寫文件頭(對於某些沒有文件頭的封裝格式,不需要此函數。比如說MPEG2TS)
avformat_write_header(avformat_context, NULL);
//打開YUV文件
const char *c_inFilePath = [inFilePath UTF8String];
FILE *in_file = fopen(c_inFilePath, "rb");
if (in_file == NULL) {
NSLog(@"YUV文件打開失敗");
return;
}
//第十步:初始化音頻採樣數據幀緩衝區
AVFrame *av_frame = av_frame_alloc();
av_frame->nb_samples = avcodec_context->frame_size;
av_frame->format = avcodec_context->sample_fmt;
//得到音頻採樣數據緩衝區大小
int buffer_size = av_samples_get_buffer_size(NULL,
avcodec_context->channels,
avcodec_context->frame_size,
avcodec_context->sample_fmt,
1);
//創建緩衝區->存儲音頻採樣數據->一幀數據
uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);
avcodec_fill_audio_frame(av_frame,
avcodec_context->channels,
avcodec_context->sample_fmt,
(const uint8_t *)out_buffer,
buffer_size,
1);
//第十二步:創建音頻壓縮數據->幀緩存空間
AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);
//第十三步:循環讀取視頻像素數據格式->編碼壓縮->視頻壓縮數據格式
int frame_current = 1;
int i = 0, ret = 0;
//第八步:循環編碼每一幀視頻
//即將AVFrame(存儲YUV像素數據)編碼爲AVPacket(存儲H.264等格式的碼流數據)
while (true) {
//1、讀取一幀音頻採樣數據
if (fread(out_buffer, 1, buffer_size, in_file) <= 0) {
NSLog(@"Failed to read raw data! \n");
break;
} else if (feof(in_file)) {
break;
}
//2、設置音頻採樣數據格式
//將outbuffer->av_frame格式
av_frame->data[0] = out_buffer;
av_frame->pts = i;
i++;
//3、編碼一幀音頻採樣數據->得到音頻壓縮數據->aac
//採用新的API
//3.1 發送一幀音頻採樣數據
ret = avcodec_send_frame(avcodec_context, av_frame);
if (ret != 0) {
NSLog(@"Failed to send frame! \n");
return;
}
//3.2 編碼一幀音頻採樣數據
ret = avcodec_receive_packet(avcodec_context, av_packet);
if (ret == 0) {
//第九步:將編碼後的音頻碼流寫入文件
NSLog(@"當前編碼到了第%d幀", frame_current);
frame_current++;
av_packet->stream_index = audio_st->index;
ret = av_write_frame(avformat_context, av_packet);
if (ret < 0) {
NSLog(@"寫入失敗! \n");
return;
}
} else {
NSLog(@"Failed to encode! \n");
return;
}
}
//第十步:輸入的像素數據讀取完成後調用此函數。用於輸出編碼器中剩餘的AVPacket。
ret = flush_encoder(avformat_context, 0);
if (ret < 0) {
NSLog(@"Flushing encoder failed\n");
return;
}
//第十一步:寫文件尾(對於某些沒有文件頭的封裝格式,不需要此函數。比如說MPEG2TS)
av_write_trailer(avformat_context);
//第十二步:釋放內存,關閉編碼器
avcodec_close(avcodec_context);
av_free(av_frame);
av_free(out_buffer);
av_packet_free(&av_packet);
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
fclose(in_file);
}