日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

首先開始的時候我們插入一張雷神大大的圖幫助大家理解一下我們今天的操作究竟屬于那一步。

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

從上圖可以看出我們要做的,就是將像素層的 YUV 格式,編碼出編碼層的 h264數(shù)據(jù)。

首先熟悉一下今天我們要用到的 ffmpeg 中的函數(shù)和結(jié)構(gòu)體

  • AVFormatContext: 數(shù)據(jù)文件操作者,主要是用于存儲音視頻封裝格式中包含的信息, 在工程當(dāng)中占著具足輕重的地位,因為很多函數(shù)都要用到它作為參數(shù)。同時,它也是我們進(jìn)行解封裝的功能結(jié)構(gòu)體。
  • AVOutputFormat: 輸出的格式,包括音頻封裝格式、視頻裝格式、字幕封裝格式,所有封裝格式都在 AVCodecID 這個枚舉類型上面了
  • AVStream: 一個裝載著視頻/音頻流信息的結(jié)構(gòu)體,包括音視頻流的長度,元數(shù)據(jù)信息,其中 index 屬性用于標(biāo)識視頻/音頻流。
  • AVCodecContext: 這個結(jié)構(gòu)體十分龐大,但它的主要是用于編碼使用的,結(jié)構(gòu)體中的的 AVCodec *codec 就是編碼所采用的編碼器器, 當(dāng)然,這個結(jié)構(gòu)體中要存入視頻的基本參數(shù),例如寬高等,存入音頻的基本參數(shù),聲道,采樣率等。
  • AVCodec:編碼器,設(shè)置編碼類型,像素格式,視頻寬高,fps(每秒幀數(shù)), 用于編解碼音視頻編碼層使用。
  • AVIOContext:用于管理輸入輸出結(jié)構(gòu)體。例如解碼的情況下,將一個視頻文件中的數(shù)據(jù)先從硬盤中讀入到結(jié)構(gòu)體中的 buffer 中,然后送給解碼器用于解碼,后面我們會用到。
  • AVFrame: 結(jié)構(gòu)體一般用于存儲原始數(shù)據(jù)(即非壓縮數(shù)據(jù),例如對視頻來說是YUV,RGB,對音頻來說是PCM),此外還包含了一些相關(guān)的信息。比如說,解碼的時候存儲了宏塊類型表,QP表,運動矢量表等數(shù)據(jù)。編碼的時候也存儲了相關(guān)的數(shù)據(jù)。因此在使用FFMPEG進(jìn)行碼流分析的時候,AVFrame是一個很重要的結(jié)構(gòu)體。

好了,上面就是我們這次解封裝用到的結(jié)構(gòu)體的大概解析,那么我們就上代碼,好好分析一番。

1、先取個霸氣點的函數(shù)名,通過輸入一個 yuv 文件路徑,然后將文件數(shù)據(jù)進(jìn)行編碼,輸出 H264文件。

yuvCodecToVideoH264(const char *input_file_name)

2、打開輸入的 yuv 文件, 并設(shè)置我們 h264 文件的輸出路徑,

FILE *in_file = fopen(input_file, "rb");  
// 因為我們在 IOS 工程當(dāng)中,所以輸出路徑當(dāng)然要設(shè)置本機(jī)的路徑了
const char* out_file = [[NSTemporaryDirectory() stringByAppendingPathComponent:@"dash.h264"] cStringUsingEncoding:NSUTF8StringEncoding];

3、獲取 yuv 視頻中的信息

// 注冊 ffmpeg 中的所有的封裝、解封裝 和 協(xié)議等,當(dāng)然,你也可用以下兩個函數(shù)代替  
// * @see av_register_input_format()
// * @see av_register_output_format()
 av_register_all();

//  用作之后寫入視頻幀并編碼成 h264,貫穿整個工程當(dāng)中
AVFormatContext* pFormatCtx;
pFormatCtx = avformat_alloc_context();

// 通過這個函數(shù)可以獲取輸出文件的編碼格式, 那么這里我們的 fmt 為 h264 格式(AVOutputFormat *)
fmt = av_guess_format(NULL, out_file, NULL);
pFormatCtx->oformat = fmt;

4、將輸出文件中的數(shù)據(jù)讀入到程序的 buffer 當(dāng)中,方便之后的數(shù)據(jù)寫入,也可以說緩存數(shù)據(jù)寫入

// 打開文件的緩沖區(qū)輸入輸出,flags 標(biāo)識為  AVIO_FLAG_READ_WRITE ,可讀寫
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
  printf("Failed to open output file! n");
  return;
}

5、創(chuàng)建流媒體數(shù)據(jù),規(guī)范流媒體的編碼格式,設(shè)置視頻流的 fps

AVStream* video_st;
// 通過媒體文件控制者獲取輸出文件的流媒體數(shù)據(jù),這里 AVCodec * 寫 0 , 默認(rèn)會為我們計算出合適的編碼格式
video_st = avformat_new_stream(pFormatCtx, 0);

// 設(shè)置 25 幀每秒 ,也就是 fps 為 25
video_st->time_base.num = 1;
video_st->time_base.den = 25;

if (video_st==NULL){
  return ;
}

6、為輸出文件設(shè)置編碼所需要的參數(shù)和格式

// 用戶存儲編碼所需的參數(shù)格式等等
AVCodecContext* pCodecCtx;

// 從媒體流中獲取到編碼結(jié)構(gòu)體,他們是一一對應(yīng)的關(guān)系,一個 AVStream 對應(yīng)一個  AVCodecContext
 pCodecCtx = video_st->codec;
   
// 設(shè)置編碼器的 id,每一個編碼器都對應(yīng)著自己的 id,例如 h264 的編碼 id 就是 AV_CODEC_ID_H264
pCodecCtx->codec_id = fmt->video_codec;

// 設(shè)置編碼類型為 視頻編碼
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

// 設(shè)置像素格式為 yuv 格式
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

// 設(shè)置視頻的寬高
pCodecCtx->width = 480;
pCodecCtx->height = 720;

// 設(shè)置比特率,每秒傳輸多少比特數(shù) bit,比特率越高,傳送速度越快,也可以稱作碼率,
// 視頻中的比特是指由模擬信號轉(zhuǎn)換為數(shù)字信號后,單位時間內(nèi)的二進(jìn)制數(shù)據(jù)量。
pCodecCtx->bit_rate = 400000;

// 設(shè)置圖像組層的大小。
// 圖像組層是在 MPEG 編碼器中存在的概念,圖像組包 若干幅圖像, 組頭包 起始碼、GOP 標(biāo)志等,如視頻磁帶記錄器時間、控制碼、B 幀處理碼等;
pCodecCtx->gop_size=250;

// 設(shè)置 25 幀每秒 ,也就是 fps 為 25
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;

//設(shè)置 H264 中相關(guān)的參數(shù)
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;

// 設(shè)置 B 幀最大的數(shù)量,B幀為視頻圖片空間的前后預(yù)測幀, B 幀相對于 I、P 幀來說,壓縮率比較大,也就是說相同碼率的情況下,
// 越多 B 幀的視頻,越清晰,現(xiàn)在很多打視頻網(wǎng)站的高清視頻,就是采用多編碼 B 幀去提高清晰度,
// 但同時對于編解碼的復(fù)雜度比較高,比較消耗性能與時間
pCodecCtx->max_b_frames=3;

// 可選設(shè)置
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
// 通過--preset的參數(shù)調(diào)節(jié)編碼速度和質(zhì)量的平衡。
av_dict_set(¶m, "preset", "slow", 0);

// 通過--tune的參數(shù)值指定片子的類型,是和視覺優(yōu)化的參數(shù),或有特別的情況。
// zerolatency: 零延遲,用在需要非常低的延遲的情況下,比如電視電話會議的編碼
av_dict_set(¶m, "tune", "zerolatency", 0);

順便說一下h264 當(dāng)中有片組的概念,其中編碼片分為5種,I 片、P 片、B 片、SP 片和 SI 片。

ES 碼流是 MPEG 碼流中的基本流,由視頻壓縮編碼后的視頻基 碼流(Video ES)和音頻壓縮編碼后的音頻基 碼流(Audio ES)組成。

相關(guān)視頻推薦:

音視頻開發(fā)系列-快速掌握音視頻開發(fā)基礎(chǔ)知識(視頻錄制原理、視頻播放原理、視頻基礎(chǔ)知識、音頻基礎(chǔ)知識)_嗶哩嗶哩_bilibili

音視頻學(xué)習(xí)最佳實踐—從FFmpeg到流媒體服務(wù)器開發(fā)_嗶哩嗶哩_bilibili

【免費】
FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級開發(fā)-學(xué)習(xí)視頻教程-騰訊課堂

需要更多ffmpeg/webrtc..音視頻流媒體開發(fā)學(xué)習(xí)資料加群812855908領(lǐng)取

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

以下順帶一張 ES 碼流的結(jié)構(gòu)圖片,作為記錄學(xué)習(xí)之用

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

ES 碼流采用圖像序列(PS)、圖像組(GOP)、圖像(P)、片(slice)、宏塊(MB)、塊(B)六層結(jié)構(gòu)。

(1)圖像序列層,圖像序列包括若干 GOP,序列頭包 起始碼和序列參數(shù),如檔次、級別、彩色圖像格式、幀場選擇等等;

(2)圖像組層,圖像組包 若干幅圖像,組頭包 起始碼、GOP 標(biāo)志等,如視頻磁帶記錄器時間、控制碼、B 幀處理碼等;

(3)圖像層,一幅圖像包 若干片,頭信息中有起始碼、P 標(biāo)志,如時間、參考幀號、圖像類型、MV、分級等;

(4)片層,片是最小的同步單位,包 若干宏塊,片頭中有起始碼、片地址、量化步長等;

(5)宏塊層,宏塊由 4 個 8×8 亮度塊和 2 個色度塊組成,宏塊頭包括宏塊地址、宏塊類型、運動矢量等。 7、printf(輸出) 一些關(guān)于輸出格式的詳細(xì)數(shù)據(jù),例如時間,比特率,數(shù)據(jù)流,容器,元數(shù)據(jù),輔助數(shù)據(jù),編碼,時間戳等等

av_dump_format(pFormatCtx, 0, out_file, 1);

8、設(shè)置編碼器

// 通過 codec_id 找到對應(yīng)的編碼器
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
  printf("Can not find encoder! n");
  return;
}

// 打開編碼器,并設(shè)置參數(shù) param
if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){
  printf("Failed to open encoder! n");
  return;
}

9、設(shè)置原始數(shù)據(jù) AVFrame

AVFrame *pFrame = av_frame_alloc();// 通過像素格式(這里為 YUV)獲取圖片的真實大小,例如將 480 * 720 轉(zhuǎn)換成 int 類型int picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);// 將 picture_size 轉(zhuǎn)換成字節(jié)數(shù)據(jù),byteunsigned char *picture_buf = (uint8_t *)av_malloc(picture_size);// 設(shè)置原始數(shù)據(jù) AVFrame 的每一個frame 的圖片大小,AVFrame 這里存儲著 YUV 非壓縮數(shù)據(jù)avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

10、準(zhǔn)備寫入數(shù)據(jù)之前,當(dāng)然要先寫編碼的頭部了

// 編寫 h264 封裝格式的文件頭部,基本上每種編碼都有著自己的格式的頭部,想看具體實現(xiàn)的同學(xué)可以看看 h264 的具體實現(xiàn)
int ret = avformat_write_header(pFormatCtx,NULL);
if (ret < 0) {
printf("write header is failed");
return;
}

這里順便記錄一下, h264 原始碼流,又稱為原始碼流,都是由一個一個的 NALU 組成的,結(jié)構(gòu)體如下

enum nal_unit_type_e
{
NAL_UNKNOWN = 0, // 未使用
NAL_SLICE = 1, // 不分區(qū)、非 IDR 圖像的片
NAL_SLICE_DPA = 2, // 片分區(qū) A
NAL_SLICE_DPB = 3, // 片分區(qū) B
NAL_SLICE_DPC = 4, // 片分區(qū) C
NAL_SLICE_IDR = 5, /* ref_idc != 0 / // 序列參數(shù)集
NAL_SEI = 6, / ref_idc == 0 / // 圖像參數(shù)集
NAL_SPS = 7, // 分界符
NAL_PPS = 8, // 序列結(jié)束
NAL_AUD = 9, // 碼流結(jié)束
NAL_FILLER = 12, // 填充
/ ref_idc == 0 for 6,9,10,11,12 */
};
enum nal_priority_e // 優(yōu)先級
{
NAL_PRIORITY_DISPOSABLE = 0,
NAL_PRIORITY_LOW = 1,
NAL_PRIORITY_HIGH = 2,
NAL_PRIORITY_HIGHEST = 3,
};

typedef struct
{
int startcodeprefix_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
unsigned len; //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
unsigned max_size; //! Nal Unit Buffer size
int forbidden_bit; //! should be always FALSE
int nal_reference_idc; //! NALU_PRIORITY_xxxx
int nal_unit_type; //! NALU_TYPE_xxxx
char *buf; //! contains the first byte followed by the EBSP
} NALU_t;

11、創(chuàng)建編碼后的數(shù)據(jù) AVPacket 結(jié)構(gòu)體來存儲 AVFrame 編碼后生成的數(shù)據(jù)

AVCodec* pCodec;
av_new_packet(&pkt,picture_size);

>其實從這里看出 AVPacket 跟 AVFrame 的關(guān)系如下

編碼前:AVFrame

編碼后:AVPacket

12、寫入 yuv 數(shù)據(jù)到 AVFrame 結(jié)構(gòu)體中

// 設(shè)置 yuv 數(shù)據(jù)中 y 圖的寬高
int y_size = pCodecCtx->width * pCodecCtx->height;

for (int i=0; i<framenum; i++){
//Read raw YUV data
if (fread(picture_buf, 1, y_size3/2, in_file) <= 0){
printf("Failed to read raw data! n");
return ;
}else if(feof(in_file)){
break;
}
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size5/4; // V
//PTS
//pFrame->pts=i;
// 設(shè)置這一幀的顯示時間
pFrame->pts=i(video_st->time_base.den)/((video_st->time_base.num)25);
int got_picture=0;
// 利用編碼器進(jìn)行編碼,將 pFrame 編碼后的數(shù)據(jù)傳入 pkt 中
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! n");
return ;
}

// 編碼成功后寫入 AVPacket 到 輸入輸出數(shù)據(jù)操作著 pFormatCtx 中,當(dāng)然,記得釋放內(nèi)存
if (got_picture==1){
printf("Succeed to encode frame: %5dtsize:%5dn",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
}

13、flush 編碼

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;

// 確認(rèn)如果
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
      CODEC_CAP_DELAY))
    return 0;
while (1) {
    enc_pkt.data = NULL;
    enc_pkt.size = 0;
    av_init_packet(&enc_pkt);
    ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                 NULL, &got_frame);
    av_frame_free(NULL);
    if (ret < 0)
        break;
    if (!got_frame){
        ret=0;
        break;
    }
    printf("Flush Encoder: Succeed to encode 1 frame!tsize:%5dn",enc_pkt.size);
    /* mux encoded frame */
    ret = av_write_frame(fmt_ctx, &enc_pkt);
    if (ret < 0)
        break;
}
return ret;
}

int ret2 = flush_encoder(pFormatCtx,0);
if (ret2 < 0) {
printf("Flushing encoder failedn");
return;
}

14、我們上面寫完了編碼頭、編碼數(shù)據(jù),當(dāng)然也要寫入編碼的尾部表示結(jié)束了啦,這樣才是一個完整的編碼格式嘛

// 寫入數(shù)據(jù)流尾部到輸出文件當(dāng)中,并釋放文件的私有數(shù)據(jù)
av_write_trailer(pFormatCtx);

15、釋放我們之前創(chuàng)建的內(nèi)存

if (video_st){
// 關(guān)閉編碼器
avcodec_close(video_st->codec);
// 釋放 AVFrame
av_free(pFrame);
// 釋放圖片 buf,就是 free() 函數(shù),硬要改名字,當(dāng)然這是跟適應(yīng)編譯環(huán)境有關(guān)系的
av_free(picture_buf);
}

// 關(guān)閉輸入數(shù)據(jù)的緩存
avio_close(pFormatCtx->pb);
// 釋放 AVFromatContext 結(jié)構(gòu)體
avformat_free_context(pFormatCtx);

// 關(guān)閉輸入文件
fclose(in_file);

---- 好了,寫到這里,我們首先要做的就是利用就把下面這個 .yuv 文件放到工程當(dāng)中,如下圖

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

然后在 `- (void)viewDidLoad `方法中使用如下代碼

const char *input_file = [[[NSBundle mainBundle] pathForResource:@"FFmpegTest" ofType:@"yuv"] cStringUsingEncoding:NSUTF8StringEncoding];

yuvCodecToVideoH264(input_file);

然后運行,瞬間, 利用同步推打開我們工程的系統(tǒng),看到我們就得到我們想要的東西了

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

---- 有些小伙伴可能在編譯的時候遇到錯誤,那是因為函數(shù)當(dāng)中一些用到的工程庫并沒有鏈接到工程中,可以在工程的 General->Linked Frameworks and Libraries 檢查如下圖

FFmpeg 開發(fā)——將視頻 YUV 格式編碼成 H264

 

好了,先寫這么多了

分享到:
標(biāo)簽:FFmpeg
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定