音視頻編解碼:視頻編解碼基礎(chǔ)1-FFmpeg結(jié)構(gòu)與API摘要
音視頻編解碼:視頻編解碼基礎(chǔ)2-FFmpeg解碼實(shí)踐
音視頻編解碼:視頻編解碼基礎(chǔ)篇3-FFmpeg轉(zhuǎn)碼實(shí)踐
音視頻編解碼:視頻編解碼基礎(chǔ)篇4-FFmpeg解碼播放
一.視頻解碼
本地視頻和網(wǎng)路視頻解碼,由于本地視頻解碼某種意義上相當(dāng)于網(wǎng)路視頻解碼的子集這里所以直接上網(wǎng)路解碼.
解碼初始化
avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)這里的options也就是代碼中的參數(shù)四,可以根據(jù)具體情況進(jìn)行具體分析配置
1.畫(huà)質(zhì)優(yōu)化
原生的ffmpeg參數(shù)在對(duì)1920x1080的RTSP流進(jìn)行播放時(shí),花屏現(xiàn)象很嚴(yán)重长窄,根據(jù)網(wǎng)上查的資料而钞,可以通過(guò)增大“buffer_size”參數(shù)來(lái)提高畫(huà)質(zhì)未桥,減少花屏現(xiàn)象
如:av_dict_set(&options, "buffer_size", "1080000", 0);
2.RTSP連接不上導(dǎo)致卡死的問(wèn)題
原生的ffmpeg參數(shù)在打開(kāi)RTSP流時(shí)旭旭,若連接不上,會(huì)出現(xiàn)卡死在打開(kāi)函數(shù)的情況,在有些情況下這是很不好的,可以通過(guò)設(shè)置超時(shí)來(lái)改變卡死的情況
如設(shè)置20s超時(shí):av_dict_set(&options, "stimeout", "20000000", 0); //設(shè)置超時(shí)斷開(kāi)連接時(shí)間
3.其他
可以設(shè)置的參數(shù)還有很多窍蓝,如可以設(shè)置連接為TCP,設(shè)置最大延時(shí)等等
av_dict_set(&options, "max_delay", "300000", 0);
av_dict_set(&options, "rtsp_transport", "tcp", 0); //以u(píng)dp方式打開(kāi)繁成,如果以tcp方式打開(kāi)將udp替換為tcp
//第一步:組冊(cè)組件
av_register_all();
avformat_network_init();
//第二步:打開(kāi)封裝格式->打開(kāi)文件
//參數(shù)一:封裝格式上下文
//作用:保存整個(gè)視頻信息(解碼器、編碼器等等...)
//信息:碼率淑玫、幀率等...
AVFormatContext* avformat_context = avformat_alloc_context();
//參數(shù)二:視頻路徑
const char *url = [urlString cStringUsingEncoding:NSUTF8StringEncoding];
//參數(shù)三:指定輸入的格式
//參數(shù)四:設(shè)置默認(rèn)參數(shù) AVDictionary* options = NULL;
int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
if (avformat_open_input_result != 0){
NSLog("打開(kāi)文件失敗");
if (formatCtx){
釋放上下文
avformat_free_context(avformat_context);
}
return;
}
//第三步:查找視頻流->拿到視頻信息
//參數(shù)一:封裝格式上下文
//參數(shù)二:指定默認(rèn)配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
if (avformat_find_stream_info_result < 0){
NSLog(@"查找失敗");
NSLog(@"avformat_find_stream_info error, %s", av_err2str(avformat_find_stream_info_result));
return;
}
//打印流信息
/*Input #0, rtsp, from 'rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov':
Metadata:
title : BigBuckBunny_175k.mov
Duration: 00:09:56.46, start: 0.000000, bitrate: N/A
Stream #0:0: Audio: aac (LC), 48000 Hz, stereo, fltp
Stream #0:1: Video: h264 (Constrained Baseline), yuv420p(progressive), 240x160, 24 fps, 24 tbr, 90k tbn, 48 tbc*/
av_dump_format(avformat_context, 0, url, false);
//第四步:查找視頻解碼器
//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尊搬、根據(jù)視頻流索引,獲取解碼器上下文
AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
//3土涝、根據(jù)解碼器上下文佛寿,獲得解碼器ID,然后查找解碼器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打開(kāi)解碼器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0){
NSLog(@"打開(kāi)解碼器失敗");
return;
}
//測(cè)試一下
//打印信息
NSLog(@"解碼器名稱:%s", avcodec->name);
//第六步:讀取視頻壓縮數(shù)據(jù)->循環(huán)讀取
//1但壮、分析av_read_frame參數(shù)
//參數(shù)一:封裝格式上下文
//參數(shù)二:一幀壓縮數(shù)據(jù) = 一張圖片
//av_read_frame()
//結(jié)構(gòu)體大小計(jì)算:字節(jié)對(duì)齊原則
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//3.2 解碼一幀視頻壓縮數(shù)據(jù)->進(jìn)行解碼(作用:用于解碼操作)
//開(kāi)辟一塊內(nèi)存空間
AVFrame* avframe_in = av_frame_alloc();
int decode_result = 0;
//4冀泻、注意:在這里我們不能夠保證解碼出來(lái)的一幀視頻像素?cái)?shù)據(jù)格式是yuv格式
//參數(shù)一:源文件->原始視頻像素?cái)?shù)據(jù)格式寬
//參數(shù)二:源文件->原始視頻像素?cái)?shù)據(jù)格式高
//參數(shù)三:源文件->原始視頻像素?cái)?shù)據(jù)格式類型
//參數(shù)四:目標(biāo)文件->目標(biāo)視頻像素?cái)?shù)據(jù)格式寬
//參數(shù)五:目標(biāo)文件->目標(biāo)視頻像素?cái)?shù)據(jù)格式高
//參數(shù)六:目標(biāo)文件->目標(biāo)視頻像素?cái)?shù)據(jù)格式類型
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);
//創(chuàng)建一個(gè)yuv420視頻像素?cái)?shù)據(jù)格式緩沖區(qū)(一幀數(shù)據(jù))
AVFrame* avframe_yuv420p = av_frame_alloc();
//給緩沖區(qū)設(shè)置類型->yuv420類型
//得到Y(jié)UV420P緩沖區(qū)大小
//參數(shù)一:視頻像素?cái)?shù)據(jù)格式類型->YUV420P格式
//參數(shù)二:一幀視頻像素?cái)?shù)據(jù)寬 = 視頻寬
//參數(shù)三:一幀視頻像素?cái)?shù)據(jù)高 = 視頻高
//參數(shù)四:字節(jié)對(duì)齊方式->默認(rèn)是1
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
//開(kāi)辟一塊內(nèi)存空間
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//向avframe_yuv420p->填充數(shù)據(jù)
//參數(shù)一:目標(biāo)->填充數(shù)據(jù)(avframe_yuv420p)
//參數(shù)二:目標(biāo)->每一行大小
//參數(shù)三:原始數(shù)據(jù)
//參數(shù)四:目標(biāo)->格式類型
//參數(shù)五:寬
//參數(shù)六:高
//參數(shù)七:字節(jié)對(duì)齊方式
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數(shù)據(jù)寫(xiě)入.yuv文件中
//打開(kāi)寫(xiě)入文件
const char *outfile = [outFilePath UTF8String];
FILE* file_yuv420p = fopen(outfile, "wb+");
if (file_yuv420p == NULL){
NSLog(@"輸出文件打開(kāi)失敗");
return;
}
int currentIndex = 0;
while (av_read_frame(avformat_context, packet) >= 0){
// >=:讀取到了
// <0:讀取錯(cuò)誤或者讀取完畢
// 2、是否是我們的視頻流
if (packet->stream_index == av_stream_index){
//第七步:解碼
//學(xué)習(xí)一下C基礎(chǔ)蜡饵,結(jié)構(gòu)體
//3弹渔、解碼一幀壓縮數(shù)據(jù)->得到視頻像素?cái)?shù)據(jù)->yuv格式
//采用新的API
//3.1 發(fā)送一幀視頻壓縮數(shù)據(jù)
avcodec_send_packet(avcodec_context, packet);
//3.2 解碼一幀視頻壓縮數(shù)據(jù)->進(jìn)行解碼(作用:用于解碼操作)
decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
if (decode_result == 0){
//解碼成功
//4、注意:在這里我們不能夠保證解碼出來(lái)的一幀視頻像素?cái)?shù)據(jù)格式是yuv格式
//視頻像素?cái)?shù)據(jù)格式很多種類型: yuv420P溯祸、yuv422p肢专、yuv444p等等...
//保證:我的解碼后的視頻像素?cái)?shù)據(jù)格式統(tǒng)一為yuv420P->通用的格式
//進(jìn)行類型轉(zhuǎn)換: 將解碼出來(lái)的視頻像素點(diǎn)數(shù)據(jù)格式->統(tǒng)一轉(zhuǎn)類型為yuv420P
//sws_scale作用:進(jìn)行類型轉(zhuǎn)換的yuv420P,<~>RGB
//參數(shù)一:視頻像素?cái)?shù)據(jù)格式上下文
//參數(shù)二:原來(lái)的視頻像素?cái)?shù)據(jù)格式->輸入數(shù)據(jù)
//參數(shù)三:原來(lái)的視頻像素?cái)?shù)據(jù)格式->輸入畫(huà)面每一行大小
//參數(shù)四:原來(lái)的視頻像素?cái)?shù)據(jù)格式->輸入畫(huà)面每一行開(kāi)始位置(填寫(xiě):0->表示從原點(diǎn)開(kāi)始讀取)
//參數(shù)五:原來(lái)的視頻像素?cái)?shù)據(jù)格式->輸入數(shù)據(jù)行數(shù)
//參數(shù)六:轉(zhuǎn)換類型后視頻像素?cái)?shù)據(jù)格式->輸出數(shù)據(jù)
//參數(shù)七:轉(zhuǎn)換類型后視頻像素?cái)?shù)據(jù)格式->輸出畫(huà)面每一行大小
sws_scale(swscontext,
(const uint8_t *const *)avframe_in->data,
avframe_in->linesize,
0,
avcodec_context->height,
avframe_yuv420p->data,
avframe_yuv420p->linesize);
//1:直接顯示視頻上面去
//2:寫(xiě)入yuv文件格式
//5、將yuv420p數(shù)據(jù)寫(xiě)入.yuv文件中
//5.1 計(jì)算YUV大小
//分析一下原理?
//Y表示:亮度
//UV表示:色度
//YUV420P格式:Y結(jié)構(gòu)表示一個(gè)像素(一個(gè)像素對(duì)應(yīng)一個(gè)Y)
4個(gè)像素點(diǎn)對(duì)應(yīng)一個(gè)(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 寫(xiě)入.yuv文件
//Y數(shù)據(jù)
fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
//U數(shù)據(jù)
fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
//V數(shù)據(jù)
fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
currentIndex++;
NSLog(@"解碼第%d幀", currentIndex);
}
}
}
釋放內(nèi)存資源焦辅,關(guān)閉解碼器
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);