音視頻開發—使用FFmpeg將YUV文件編碼成H264裸流文件 C語言實現

文章目錄

    • 1.準備工作
    • 2.壓縮編碼工作流程
    • 3.詳細步驟
      • 1. 初始化日志和參數檢查
      • 2. 輸入/輸出文件的打開
      • 3. 查找和初始化編碼器
      • 4. 打開編碼器
      • 5. 幀內存的分配和初始化
      • 6. 設置轉換上下文(SWS)
      • 7. 讀取和轉換數據
      • 8. 編碼過程
      • 9. 資源清理
    • 4.完整示例代碼

1.準備工作

原始YUV文件,只包含圖像的原始信息,無論是播放還是進行H264壓縮編碼,都需要知曉文件格式。

  • 像素格式:常見的格式有YUV420P、YUV422P、YUV420打包格式:NV12 NV21 等等

  • 分辨率:寬度和高度

FFmpeg播放YUV文件示例:

ffplay -f rawvideo -pixel_format yuv422p -video_size 1920x1080 input_1920x1080_yuv422p_1.yuv 

命令解析:

  • ffplay: 這是用于播放視頻和音頻文件的命令行媒體播放器。
  • -f rawvideo: 這個選項指定輸入文件的格式。rawvideo 表示輸入文件是一個未經壓縮處理的視頻數據流。
  • -pixel_format yuv422p: 指定了像素格式。yuv422p 是一種 YUV 格式,其中 Y、U 和 V 分量是平面分隔的(p 表示平面),并且色度(U 和 V)采樣是每兩個像素共享一次(即 4:2:2 采樣)。這個格式常見于專業視頻編輯和后期處理中。
  • -video_size 1920x1080: 設置視頻的分辨率為 1920x1080 像素,這通常是全高清視頻的標準分辨率。
  • input_1920x1080_yuv422p_1.yuv: 這是輸入文件的路徑和名稱。文件擴展名 .yuv 通常用于存儲原始的 YUV 格式視頻數據。

效果如下:這里以一張高清圖片為示例

在這里插入圖片描述

2.壓縮編碼工作流程

總體流程:在這里插入圖片描述

需要注意的是:編碼H264一般使用X264的居多,要求輸入的格式一般為YUV420格式,因此如果是YUV422需要轉換為YUV420格式

3.詳細步驟

1. 初始化日志和參數檢查

 av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");if (argc < 5){printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);return -1;}
  • 使用 av_log_set_level 設置日志級別,用于調試信息輸出。
  • 檢查傳入的命令行參數數量,確保提供了足夠的信息來進行后續處理。

2. 輸入/輸出文件的打開

 // 讀取YUV 文件input_file = fopen(input_filename, "rb");if (!input_file){fprintf(stderr, "Could not open input file '%s'\n", input_filename);CLEANUP(failure);return -1;}// 打開要寫入的文件output_file = fopen(output_filename, "wb");if (!output_file){fprintf(stderr, "Could not open output file '%s'\n", output_filename);CLEANUP(failure);return -1;}
  • 嘗試打開輸入文件(YUV數據源)和輸出文件(存儲編碼后的視頻)。如果文件打開失敗,跳轉到 failure 標簽進行資源釋放和退出。

3. 查找和初始化編碼器

  • 查找H264編碼器。如果找不到編碼器,跳轉到 failure
    // 查找編碼器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");CLEANUP(failure);return -1;}// 分配編碼器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");CLEANUP(failure);return -1;}
  • 為編碼器分配上下文,并設置編碼參數,如比特率、分辨率、幀率等。這些參數對最終視頻的質量和文件大小有直接影響。
// 設置編碼器的參數codec_ctx->bit_rate = 4000000;  //與圖像質量直接掛鉤codec_ctx->height = height;codec_ctx->width = width;codec_ctx->time_base = (AVRational){1, 25};codec_ctx->framerate = (AVRational){25, 1};codec_ctx->gop_size = 10;codec_ctx->max_b_frames = 0;             // 設置成0,可以減少編碼器負擔codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P進行編碼

4. 打開編碼器

  • 嘗試打開已配置的編碼器。如果編碼器無法打開,跳轉到 failure
    // 打開編碼器if (avcodec_open2(codec_ctx, codec, NULL) < 0){av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");CLEANUP(failure);return -1;}

5. 幀內存的分配和初始化

  • 分配內存給原始幀 frame 和用于轉換的幀 sws_frame。如果分配失敗,進行資源清理并跳轉到 failure
  • 對這兩個幀進行格式設置和內存分配。
  // 分配原始幀和轉換幀frame = av_frame_alloc();sws_frame = av_frame_alloc();if (!frame || !sws_frame){fprintf(stderr, "Could not allocate video frame\n");fclose(input_file);fclose(output_file);avcodec_free_context(&codec_ctx);if (frame)av_frame_free(&frame);if (sws_frame)av_frame_free(&sws_frame);return -1;}frame->format = codec_ctx->pix_fmt;frame->width = width;frame->height = height;ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);if (ret < 0){CLEANUP(failure);return -1;}sws_frame->format = AV_PIX_FMT_YUV422P;sws_frame->width = width;sws_frame->height = height;ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);if (ret < 0){fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");CLEANUP(failure);return -1;}

記得也要packet進行初始化,調了半個小時才發現,忘給AVPacket初始化了,一直報內存錯誤。。。。。。

    // 分配數據包av_init_packet(&packet); // 初始化packetpacket.data = NULL;packet.size = 0;

6. 設置轉換上下文(SWS)

  • 初始化用于像素格式轉換的 SwsContext。這個上下文負責將YUV422P格式轉換成編碼器需要的YUV420P格式。
    // 創建SWS上下文sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);if (!sws_ctx){fprintf(stderr, "Could not initialize the conversion context\n");fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;}

7. 讀取和轉換數據

  • 從輸入文件中讀取YUV422P數據到 sws_frame,然后使用 sws_scale 函數進行格式轉換,并存儲到 frame
  • 如果讀取失敗,輸出錯誤信息并跳轉到 failure
fread(sws_frame->data[0], 1, width * height * 2, input_file)
sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);

8. 編碼過程

  • 將轉換后的幀發送到編碼器。
   // 數據送入編碼器ret = avcodec_send_frame(codec_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending frame for encoding\n");CLEANUP(failure);}
  • 循環調用 avcodec_receive_packet 來接收編碼后的數據,并將其寫入輸出文件。
  • 在循環中,使用 av_packet_unref 來釋放已經寫入文件的數據包內存。
// 接收編碼后的數據包while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}// 寫入到文件fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}
  • 最后,發送一個空幀到編碼器以刷新所有待處理的幀。
    // 刷新編碼器ret = avcodec_send_frame(codec_ctx, NULL);if (ret < 0){fprintf(stderr, "Error sending flush packet to encoder\n");CLEANUP(failure);}while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}

9. 資源清理

  • 在正常執行結束和錯誤處理 (failure 標簽) 中,關閉文件、釋放分配的內存和其他資源。
  fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);

4.完整示例代碼

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>
// 錯誤處理和資源釋放
#define CLEANUP(label) \do                 \{                  \goto label;    \} while (0)int main(int argc, char *argv[])
{av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");if (argc < 5){printf("Usage: %s <input file> <width> <height> <output file>\n", argv[0]);return -1;}// 程序輸入const char *input_filename = argv[1];int width = atoi(argv[2]);int height = atoi(argv[3]);const char *output_filename = argv[4];// 相關變量初始化AVCodecContext *codec_ctx = NULL;AVCodec *codec = NULL;AVFrame *frame = NULL;AVFrame *sws_frame = NULL;AVPacket packet;struct SwsContext *sws_ctx = NULL;FILE *input_file = NULL;FILE *output_file = NULL;int ret;// 讀取YUV 文件input_file = fopen(input_filename, "rb");if (!input_file){fprintf(stderr, "Could not open input file '%s'\n", input_filename);CLEANUP(failure);return -1;}// 打開要寫入的文件output_file = fopen(output_filename, "wb");if (!output_file){fprintf(stderr, "Could not open output file '%s'\n", output_filename);CLEANUP(failure);return -1;}// 查找編碼器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){av_log(NULL, AV_LOG_ERROR, "H264 Codec not found.....\n");CLEANUP(failure);return -1;}// 分配編碼器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.....\n");CLEANUP(failure);return -1;}// 設置編碼器的參數codec_ctx->bit_rate = 4000000;  //與圖像質量codec_ctx->height = height;codec_ctx->width = width;codec_ctx->time_base = (AVRational){1, 25};codec_ctx->framerate = (AVRational){25, 1};codec_ctx->gop_size = 10;codec_ctx->max_b_frames = 0;             // 設置成0,可以減少編碼器負擔codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 使用YUV420P進行編碼// 打開編碼器if (avcodec_open2(codec_ctx, codec, NULL) < 0){av_log(NULL, AV_LOG_ERROR, "Could not open codec.....\n");CLEANUP(failure);return -1;}// 分配原始幀和轉換幀frame = av_frame_alloc();sws_frame = av_frame_alloc();if (!frame || !sws_frame){fprintf(stderr, "Could not allocate video frame\n");fclose(input_file);fclose(output_file);avcodec_free_context(&codec_ctx);if (frame)av_frame_free(&frame);if (sws_frame)av_frame_free(&sws_frame);return -1;}frame->format = codec_ctx->pix_fmt;frame->width = width;frame->height = height;ret = av_image_alloc(frame->data, frame->linesize, width, height, codec_ctx->pix_fmt, 32);if (ret < 0){CLEANUP(failure);return -1;}sws_frame->format = AV_PIX_FMT_YUV422P;sws_frame->width = width;sws_frame->height = height;ret = av_image_alloc(sws_frame->data, sws_frame->linesize, width, height, AV_PIX_FMT_YUV422P, 32);if (ret < 0){fprintf(stderr, "Could not allocate raw picture buffer for SWS frame\n");CLEANUP(failure);return -1;}// 創建SWS上下文sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV422P, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);if (!sws_ctx){fprintf(stderr, "Could not initialize the conversion context\n");fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;}// 分配數據包av_init_packet(&packet); // 初始化packetpacket.data = NULL;packet.size = 0;// 讀取YUV422P數據到sws_frame中if (fread(sws_frame->data[0], 1, width * height * 2, input_file) == width * height * 2){sws_scale(sws_ctx, (const uint8_t *const *)sws_frame->data, sws_frame->linesize, 0, height, frame->data, frame->linesize);frame->pts = 0; // 設置時間戳// 數據送入編碼器ret = avcodec_send_frame(codec_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending frame for encoding\n");CLEANUP(failure);}// 接收編碼后的數據包while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}// 寫入到文件fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}}else{fprintf(stderr, "Error reading input file\n");}// 刷新編碼器ret = avcodec_send_frame(codec_ctx, NULL);if (ret < 0){fprintf(stderr, "Error sending flush packet to encoder\n");CLEANUP(failure);}while (ret >= 0){ret = avcodec_receive_packet(codec_ctx, &packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){fprintf(stderr, "Error during encoding\n");CLEANUP(failure);break;}fwrite(packet.data, 1, packet.size, output_file);av_packet_unref(&packet);}// 釋放資源fclose(input_file);fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return 0;failure:if (input_file)fclose(input_file);if (output_file)fclose(output_file);if (frame)av_freep(&frame->data[0]);if (sws_frame)av_freep(&sws_frame->data[0]);av_frame_free(&frame);av_frame_free(&sws_frame);avcodec_free_context(&codec_ctx);return -1;
}

該程序輸出結果為.h264的碼流文件,這里并沒有添加文件容器(MP4/MKV/MOV)

可以直接調用ffplay進行播放,無需指定任何參數,因為已經封裝到了NAL單元中,查看是否壓編碼成功。

可以看出有了顯著的壓縮效果,如果就降低碼率,還可以進行縮小文件大小

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45166.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45166.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45166.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

熊海CMS漏洞練習平臺的一次xss、sql注入、越權黑盒思路分析

簡介 熊海CMS是由熊海開發的一款功能豐富的網站綜合管理系統&#xff0c;廣泛應用于個人博客、個人網站以及企業網站&#xff0c;本文章用于黑盒測試&#xff0c;如果需要「源碼審計」后臺回復【「CMS」】獲取即可&#xff0c;精心準備了40多個cms源碼漏洞平臺&#xff0c;供寶…

代碼隨想錄第七天(454、383、15、18)

題目一&#xff1a;四數相加II 鏈接&#xff1a; 代碼隨想錄 思路&#xff1a;首先用雙循環遍歷構成ab的值和出現的次數&#xff0c;用字典接收&#xff0c;由于abcd0&#xff0c;因為在對c和d進行雙循環后&#xff0c;在字典中找到0-c-d&#xff0c;得出它的值也就是出現次數…

在瀏覽器控制臺中輸出js對象,為什么顏色不同,有深有淺

打開console&#xff0c;輸入自定義的javascript對象的時候&#xff0c;打開看發現對象的屬性是深紫色&#xff0c;后面有一些對象是淺紫色的&#xff0c;比如Array對象和一堆SVG,HTML,CSS開頭的對象&#xff0c;常用的prototype和__proto__也是淺紫色的。 請問這里深紫和淺紫…

【Unity】制作簡易計時器

一、創建計時器相關的變量 我們需要創建三個變量&#xff0c;分別是&#xff1a;計時時長、計時剩余時長、是否處于計時狀態。 public float duration;//計時時長 public float remain; //計時剩余時長 public bool isCount; //是否處于計時狀態 二、初始化變量 我們可以直…

什么是Maven以及如何配置Maven

T04BF &#x1f44b;專欄: 算法|JAVA|MySQL|C語言 &#x1faf5; 今天你敲代碼了嗎 文章目錄 1.Maven1.1什么是Maven1.2Maven的好處1.3使用idea創建一個Maven項目1.4Maven的核心功能1.4.1項目構建 1.5Maven倉庫1.5.2 中央倉庫1.5.3 私有服務器(私服) 1.6Maven設置國內源 1.Mave…

[pytorch]常用函數(自用)

一、公共部分 1、torch.linespace 返回一維張量&#xff0c;在start和end之間&#xff08;包括start也包括end&#xff09;的均勻間隔的steps個點&#xff0c;長度為steps。 print(torch.linspace(1,10,3)) #輸出tensor([ 1.0000, 5.5000, 10.0000]) print(torch.linspace…

文本分類--NLP-AI(八)

文本分類任務 任務簡介1.字符數值化方式1方式2 2.池化&#xff08;pooling&#xff09;3.全連接層4.歸一化函數&#xff08;Sigmoid&#xff09;5.總結 從任務抽象新的技術點Embedding層池化層 任務簡介 任務介紹&#xff1a; 字符串分類&#xff0c;根據一句話的含媽量&#…

伊利25屆校招24年社招網申入職北森測評題庫全攻略!一文通!

伊利校招社招網申測評全攻略&#x1f680; 親愛的求職小伙伴們&#xff0c;今天我要分享一份伊利校招社招網申測評的全攻略&#xff0c;希望能助你們一臂之力&#xff01; 測評概覽 伊利的網申測評分為六個部分&#xff0c;總共約60分鐘的答題時間&#xff0c;涵蓋了言語邏輯、…

避免 WebSocket 連接被拒絕

一、檢查服務器配置和權限 (一)確認服務器訪問權限 確保您的客戶端有訪問服務器的合法權限。如果服務器設置了訪問控制列表(ACL)或僅允許特定的源(Origin)進行連接,您需要確保客戶端的請求來源在允許的范圍內。例如,如果服務器只允許來自特定域名的連接,而您的客戶端從…

【微信小程序開發】如何定義公共的js函數,其它頁面可以調用

在微信小程序開發中&#xff0c;可以通過以下步驟定義和使用公共的 JS 函數&#xff0c;使得其它頁面可以調用&#xff1a; 1. 創建一個公共的 JS 文件&#xff1a;在項目的 utils 目錄下創建一個 JS 文件&#xff0c;例如 utils/util.js。 2. 定義公共函數&#xff1a;在 uti…

在word中刪除endnote參考文獻之間的空行

如圖&#xff0c;在References中&#xff0c;每個文獻之間都有空行。不建議手動刪除。打開Endnote。 打開style manager 刪除layout中的換行符。保存&#xff0c;在word中更新參考文獻即可。

Python和C++全球導航衛星系統和機器人姿態觸覺感知二分圖算法

&#x1f3af;要點 &#x1f3af;馬爾可夫隨機場網格推理學習 | &#x1f3af;二維伊辛模型四連網格模型推理 | &#x1f3af;統計物理學模型擾動與最大乘積二值反卷積 | &#x1f3af;受限玻爾茲曼機擾動和最大乘積采樣 | &#x1f3af;視覺概率生成模型測試圖像 &#x1f3…

從課本上面開始學習的51單片機究竟有什么特點,在現在的市場上還有應用嗎?

引言 51單片機&#xff0c;作為一種經典的微控制器&#xff0c;被廣泛應用于各種嵌入式系統中。盡管如今ARM架構的高性能低成本單片機在市場上占據主導地位&#xff0c;但51單片機憑借其獨特的優勢依然在某些領域保持著應用價值。本文將深入探討51單片機的特點、架構、應用以及…

ubuntu22.04 安裝boost

下載boost壓縮包&#xff0c;我這里上傳了一份1_81_0版本tar -xzvf boost_1_81_0.tar.gzcd boost_1_81_0/sudo apt install build-essential g autotools-dev libicu-dev libbz2-dev -ysudo ./bootstrap.sh --prefix/usr/./b2sudo ./b2 install 上述7步完成后&#xff0c;相關…

數學建模·模糊評價法

模糊評價法 一種解決評價問題或者得出最佳方案的方法 主觀性仍比較強 具體定義 三集&#xff1a;因素集&#xff0c;評語集和權重集&#xff0c;通過模擬矩陣的處理得到最合理的評語 具體步驟 因素集 因素集的確定不難&#xff0c;難在對分級評價時&#xff0c;對因素集的分級…

LeetCode --- 134雙周賽

題目 3206. 交替組 I 3207. 與敵人戰斗后的最大分數 3208. 交替組 II 3209. 子數組按位與值為 K 的數目 一、交替組 I & II 題目中問環形數組中交替組的長度為3的子數組個數&#xff0c;主要的問題在于它是環形的&#xff0c;我們要考慮首尾相接的情況&#xff0c;如何…

阿里新開源GPU版本的FunASR安裝避坑

#當前安裝過程沒有cpu版本順利 1.個人在自己的電腦上安裝ubantu系統&#xff0c;以便使用本身的顯卡功能(本人顯卡NVIDIA GeForce RTX 4060)&#xff08;這里需要注意&#xff0c;更新里面有附加驅動安裝驅動會導致黑屏&#xff0c;小伙伴不要心急重裝系統&#xff0c;可以ctr…

ES索引模板

在Elasticsearch中&#xff0c;索引模板&#xff08;Index Templates&#xff09;是用來預定義新創建索引的設置和映射的一種機制。當你創建了一個索引模板&#xff0c;它會包含一系列的默認設置和映射規則&#xff0c;這些規則會在滿足一定條件的新索引被創建時自動應用。 索…

UOS查看系統信息命令行

UOS查看系統信息命令行 *** Rz整理 僅供參考 *** dmidecode查看System Boot信息 midecode -t 32 dmidecode查看System Reset信息 midecode -t 23 dmidecode查看機箱信息 midecode -t chassis dmidecode查看BIOS信息 midecode -t bios dmidecode查看CPU信息 dmidecode …

leetcode 404. 左葉子之和

給定二叉樹的根節點 root &#xff0c;返回所有左葉子之和。 示例 1&#xff1a; 輸入: root [3,9,20,null,null,15,7] 輸出: 24 解釋: 在這個二叉樹中&#xff0c;有兩個左葉子&#xff0c;分別是 9 和 15&#xff0c;所以返回 24示例 2: 輸入: root [1] 輸出: 0提示: 節點…