基礎概念
縮寫 | 編碼標準 | FourCC | 說明 |
---|---|---|---|
AVC/H.264 | Advanced Video Coding | avc1 | 最常用的 H.264 編碼標識符,兼容 MP4/MOV/FMP4 等容器。 |
HEVC/H.265 | High Efficiency Video Coding | hvc1 | HEVC 視頻流在 MP4/FMP4 中常用標識符,要求存儲 NALU 的 VPS/SPS/PPS(類似 H.264 SPS/PPS)信息在 track header 內。 |
HEVC/H.265 | High Efficiency Video Coding | hev1 | 另一個 HEVC 標識符,與 hvc1 區別在于封裝方式:hev1 視頻幀中只保留原始 NALU,SPS/PPS/VPS 信息不一定放在 track header 內,而是隨幀存儲。 |
小結:FourCC 是用于 MP4/FMP4/QuickTime 等容器標識視頻編碼類型的 4 字節字符碼。
avc1(H.264)
- 用途:用于 MP4/FMP4/MOV 文件中標識 H.264 視頻流。
- NALU 類型:
- SPS (Sequence Parameter Set)
- PPS (Picture Parameter Set)
- IDR/I幀、P幀、B幀
- 封裝特點:
avcC
box 中存儲 SPS/PPS 信息(解碼初始化參數)。- 視頻幀按長度前綴或 Annex-B 封裝。
- 兼容性:
- 幾乎所有現代播放器和瀏覽器均支持。
- 廣泛用于 Web 視頻(MP4/HLS/DASH)。
hvc1 與 hev1 (H.265)
特性 | hvc1 | hev1 |
---|---|---|
SPS/PPS/VPS | 存儲在 track header(init segment)中 | 可隨幀存儲,也可在 init segment |
主要用途 | WebM/MP4/FMP4 等 MP4 封裝 | MP4/FMP4、部分硬件加速播放器 |
播放兼容性 | 現代硬件/軟件 HEVC 解碼器 | 軟件解碼器可能需要解析每幀 NALU |
注釋 | hvc1 適合片頭集中存儲參數集 | hev1 更接近原始編碼流(流媒體或片段式播放) |
注意:
- 對于
hvc1
,播放器在播放前可直接讀取 init segment 中的 VPS/SPS/PPS 初始化解碼器。 - 對于
hev1
,播放器可能需要在每個片段或每幀中解析 NALU 以獲取參數集,適合動態流式場景。
示例
C++ + FFmpeg 將 H.264/H.265 裸流封裝成 FMP4(Fragmented MP4),并設置 FourCC 為 avc1
或 hvc1
。
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/mem.h>
#include <libavutil/error.h>
}// 從裸流中提取 SPS/PPS/VPS 并生成 extradata
std::vector<uint8_t> extract_extradata(const std::vector<uint8_t>& stream_data, bool is_h265) {std::vector<uint8_t> extradata;size_t pos = 0;while (pos + 4 < stream_data.size()) {// 查找起始碼 0x00000001 或 0x000001size_t start = stream_data.find("\x00\x00\x00\x01", pos);if (start == std::string::npos) start = stream_data.find("\x00\x00\x01", pos);if (start == std::string::npos) break;size_t next_start = stream_data.find("\x00\x00\x00\x01", start + 4);if (next_start == std::string::npos) next_start = stream_data.size();uint8_t nal_unit_type = stream_data[start + 4] & 0x1F; // H.264if (is_h265) {nal_unit_type = (stream_data[start + 4] >> 1) & 0x3F; // H.265}// H.264: SPS=7, PPS=8; H.265: VPS=32, SPS=33, PPS=34if ((!is_h265 && (nal_unit_type == 7 || nal_unit_type == 8)) ||(is_h265 && (nal_unit_type == 32 || nal_unit_type == 33 || nal_unit_type == 34))) {extradata.insert(extradata.end(), stream_data.begin() + start, stream_data.begin() + next_start);}pos = next_start;}return extradata;
}// 將裸流封裝成 fMP4
int mux_fmp4(const char* input_file, const char* output_file, bool is_h265) {av_register_all();avformat_network_init();int ret;AVFormatContext* ofmt_ctx = nullptr;AVStream* out_stream = nullptr;// 創建輸出上下文ret = avformat_alloc_output_context2(&ofmt_ctx, nullptr, "mp4", output_file);if (ret < 0 || !ofmt_ctx) {std::cerr << "Could not create output context\n";return -1;}// 打開輸入裸流文件std::ifstream infile(input_file, std::ios::binary | std::ios::ate);if (!infile.is_open()) {std::cerr << "Failed to open input file\n";return -1;}auto file_size = infile.tellg();infile.seekg(0);std::vector<uint8_t> stream_data(file_size);infile.read(reinterpret_cast<char*>(stream_data.data()), file_size);// 提取 extradatastd::vector<uint8_t> extradata = extract_extradata(stream_data, is_h265);// 創建視頻流out_stream = avformat_new_stream(ofmt_ctx, nullptr);if (!out_stream) {std::cerr << "Failed to create stream\n";return -1;}AVCodecParameters* codecpar = out_stream->codecpar;codecpar->codec_type = AVMEDIA_TYPE_VIDEO;codecpar->codec_id = is_h265 ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264;codecpar->width = 1920;codecpar->height = 1080;out_stream->time_base = AVRational{1, 25};if (!extradata.empty()) {codecpar->extradata_size = extradata.size();codecpar->extradata = (uint8_t*)av_malloc(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE);memcpy(codecpar->extradata, extradata.data(), extradata.size());}// 設置 FMP4 flagsav_opt_set(ofmt_ctx->priv_data, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0);// 打開輸出文件if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {ret = avio_open(&ofmt_ctx->pb, output_file, AVIO_FLAG_WRITE);if (ret < 0) {std::cerr << "Could not open output file\n";return -1;}}// 寫文件頭ret = avformat_write_header(ofmt_ctx, nullptr);if (ret < 0) {std::cerr << "Error writing header\n";return -1;}// 分幀寫入size_t pos = 0;int64_t pts = 0;while (pos + 4 < stream_data.size()) {// 查找起始碼size_t start = stream_data.find("\x00\x00\x00\x01", pos);if (start == std::string::npos) start = stream_data.find("\x00\x00\x01", pos);if (start == std::string::npos) break;size_t next_start = stream_data.find("\x00\x00\x00\x01", start + 4);if (next_start == std::string::npos) next_start = stream_data.size();AVPacket pkt;av_init_packet(&pkt);pkt.data = stream_data.data() + start;pkt.size = next_start - start;pkt.stream_index = out_stream->index;pkt.pts = pts;pkt.dts = pts;pkt.duration = 1;pkt.flags |= AV_PKT_FLAG_KEY; // 假設每幀都是關鍵幀,可根據 nal_unit_type 判斷ret = av_interleaved_write_frame(ofmt_ctx, &pkt);if (ret < 0) {std::cerr << "Error muxing packet\n";break;}pos = next_start;pts++;}av_write_trailer(ofmt_ctx);if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {avio_close(ofmt_ctx->pb);}avformat_free_context(ofmt_ctx);std::cout << "FMP4 muxing done.\n";return 0;
}int main(int argc, char* argv[]) {if (argc < 4) {std::cerr << "Usage: " << argv[0] << " input.264|265 output.mp4 is_h265(0|1)\n";return -1;}const char* input = argv[1];const char* output = argv[2];bool is_h265 = std::atoi(argv[3]) != 0;mux_fmp4(input, output, is_h265);return 0;
}