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

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

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

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

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

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

首先熟悉一下今天我們要用到的 ffmpeg 中的函數和結構體

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

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

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

yuvCodecToVideoH264(const char *input_file_name)

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

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

3、獲取 yuv 視頻中的信息

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

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

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

4、將輸出文件中的數據讀入到程序的 buffer 當中,方便之后的數據寫入,也可以說緩存數據寫入

// 打開文件的緩沖區輸入輸出,flags 標識為  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、創建流媒體數據,規范流媒體的編碼格式,設置視頻流的 fps

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

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

if (video_st==NULL){
  return ;
}

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

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

// 從媒體流中獲取到編碼結構體,他們是一一對應的關系,一個 AVStream 對應一個  AVCodecContext
 pCodecCtx = video_st->codec;
   
// 設置編碼器的 id,每一個編碼器都對應著自己的 id,例如 h264 的編碼 id 就是 AV_CODEC_ID_H264
pCodecCtx->codec_id = fmt->video_codec;

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

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

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

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

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

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

//設置 H264 中相關的參數
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;

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

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

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

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

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

相關視頻推薦:

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

音視頻學習最佳實踐—從FFmpeg到流媒體服務器開發_嗶哩嗶哩_bilibili

【免費】
FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級開發-學習視頻教程-騰訊課堂

需要更多ffmpeg/webrtc..音視頻流媒體開發學習資料加群812855908領取

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

以下順帶一張 ES 碼流的結構圖片,作為記錄學習之用

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

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

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

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

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

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

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

av_dump_format(pFormatCtx, 0, out_file, 1);

8、設置編碼器

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

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

9、設置原始數據 AVFrame

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

10、準備寫入數據之前,當然要先寫編碼的頭部了

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

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

enum nal_unit_type_e
{
NAL_UNKNOWN = 0, // 未使用
NAL_SLICE = 1, // 不分區、非 IDR 圖像的片
NAL_SLICE_DPA = 2, // 片分區 A
NAL_SLICE_DPB = 3, // 片分區 B
NAL_SLICE_DPC = 4, // 片分區 C
NAL_SLICE_IDR = 5, /* ref_idc != 0 / // 序列參數集
NAL_SEI = 6, / ref_idc == 0 / // 圖像參數集
NAL_SPS = 7, // 分界符
NAL_PPS = 8, // 序列結束
NAL_AUD = 9, // 碼流結束
NAL_FILLER = 12, // 填充
/ ref_idc == 0 for 6,9,10,11,12 */
};
enum nal_priority_e // 優先級
{
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、創建編碼后的數據 AVPacket 結構體來存儲 AVFrame 編碼后生成的數據

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

>其實從這里看出 AVPacket 跟 AVFrame 的關系如下

編碼前:AVFrame

編碼后:AVPacket

12、寫入 yuv 數據到 AVFrame 結構體中

// 設置 yuv 數據中 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;
// 設置這一幀的顯示時間
pFrame->pts=i(video_st->time_base.den)/((video_st->time_base.num)25);
int got_picture=0;
// 利用編碼器進行編碼,將 pFrame 編碼后的數據傳入 pkt 中
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! n");
return ;
}

// 編碼成功后寫入 AVPacket 到 輸入輸出數據操作著 pFormatCtx 中,當然,記得釋放內存
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;

// 確認如果
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、我們上面寫完了編碼頭、編碼數據,當然也要寫入編碼的尾部表示結束了啦,這樣才是一個完整的編碼格式嘛

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

15、釋放我們之前創建的內存

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

// 關閉輸入數據的緩存
avio_close(pFormatCtx->pb);
// 釋放 AVFromatContext 結構體
avformat_free_context(pFormatCtx);

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

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

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

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

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

yuvCodecToVideoH264(input_file);

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

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

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

FFmpeg 開發——將視頻 YUV 格式編碼成 H264

 

好了,先寫這么多了

分享到:
標簽:FFmpeg
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

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

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定