前言 ( 廢話 ):
嗯,別問我為啥用 Unity 搞 H.264,沒為什麼,純粹只是我喜歡 Unity 而已
不喜歡 FFMPEG 的人有福了,我們直接從 Bit 流解析 H.264,不用第三方插件,也能很好的跨平台,是不是很爽 ?
而且大部分的人都是用 C/C++ 來做解碼,用 C# 等物件導向語言做的人還挺少人做的,所以我來為世界貢獻一份心力,哈哈
SPS 是甚麼我就不詳細解釋了 ( 會看我文章的人估計都是有水準的人 ? ),簡單的說就是 影片的參數集 (例如影片的長與寬),沒有 SPS 會無法解析影片的。
你可以直接往下拉到最下面看輸出結果,你就可以知道整體的運作方式了。
C #:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SPSDecoder : MonoBehaviour
{
unsafe void Start ()
{
byte[] strArray = GetSPS_ReadFile ("C:/D.h264");
// // 如果你沒有上面的 h.264 檔案測試,你可以把下面註解解開,然後把上面的 GetSPS_ReadFile() 那行註解掉
// // SPS Frame, 已去除開頭的 0x01 0x00 0x00 0x01 0x67
// byte[] strArray = new byte[] {
// 0x64, 0x00, 0x28, 0xAC, 0xD9, 0x40, 0x78, 0x02, 0x27, 0xE5, 0xC0, 0x44, 0x00, 0x00, 0x0F, 0xA4, 0x00, 0x02, 0xEE, 0x00, 0x3C, 0x60, 0xC6, 0x58, 0x00
// };
// ----------------------
int profile_idc = -1;
int level_idc = -1;
int seq_parameter_set_id = -1;
int chroma_format_idc = -1;
int bit_depth_luma_minus8 = -1;
int bit_depth_chroma_minus8 = -1;
int qpprime_y_zero_transform_bypass_flag = -1;
int seq_scaling_matrix_present_flag = -1;
int log2_max_frame_num_minus4 = -1;
int pic_order_cnt_type = -1;
int log2_max_pic_order_cnt_lsb_minus4 = -1;
int max_num_ref_frames = -1;
int gaps_in_frame_num_value_allowed_flag = -1;
int pic_width_in_mbs_minus1 = -1;
int pic_height_in_map_units_minus1 = -1;
int frame_mbs_only_flag = -1;
int mb_adaptive_frame_field_flag = -1;
int direct_8x8_inference_flag = -1;
int frame_cropping_flag = -1;
int frame_crop_left_offset = -1;
int frame_crop_right_offset = -1;
int frame_crop_top_offset = -1;
int frame_crop_bottom_offset = -1;
int vui_parameters_present_flag = -1;
// ----------------------
int bytePosition = 3, bitPosition = 0;
int dataLengthInBits = strArray.Length * 8;
profile_idc = strArray [0];
level_idc = strArray [2];
seq_parameter_set_id = get_uev_code_num (strArray, &bytePosition, &bitPosition);
if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144) {
chroma_format_idc = get_uev_code_num (strArray, &bytePosition, &bitPosition);
if (chroma_format_idc == 3) {
}
bit_depth_luma_minus8 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
bit_depth_chroma_minus8 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
qpprime_y_zero_transform_bypass_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
seq_scaling_matrix_present_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
if (seq_scaling_matrix_present_flag == 1) {
throw new UnityException ("不支援此格式的檔案");
}
log2_max_frame_num_minus4 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
pic_order_cnt_type = get_uev_code_num (strArray, &bytePosition, &bitPosition);
if (pic_order_cnt_type == 0) {
log2_max_pic_order_cnt_lsb_minus4 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
} else if (pic_order_cnt_type == 1) {
throw new UnityException ("不支援此格式的檔案");
}
max_num_ref_frames = get_uev_code_num (strArray, &bytePosition, &bitPosition);
gaps_in_frame_num_value_allowed_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
pic_width_in_mbs_minus1 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
pic_height_in_map_units_minus1 = get_uev_code_num (strArray, &bytePosition, &bitPosition);
frame_mbs_only_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
if (frame_mbs_only_flag == 0) {
mb_adaptive_frame_field_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
}
direct_8x8_inference_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
frame_cropping_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
if (frame_cropping_flag == 1) {
frame_crop_left_offset = get_uev_code_num (strArray, &bytePosition, &bitPosition);
frame_crop_right_offset = get_uev_code_num (strArray, &bytePosition, &bitPosition);
frame_crop_top_offset = get_uev_code_num (strArray, &bytePosition, &bitPosition);
frame_crop_bottom_offset = get_uev_code_num (strArray, &bytePosition, &bitPosition);
}
vui_parameters_present_flag = get_bit_at_position (strArray, &bytePosition, &bitPosition);
if (vui_parameters_present_flag == 1) {
}
}
// ----------------------
// Print
print ("profile_idc = " + profile_idc);
print ("level_idc = " + level_idc);
print ("seq_parameter_set_id = " + seq_parameter_set_id);
print ("chroma_format_idc = " + chroma_format_idc);
print ("bit_depth_luma_minus8 = " + bit_depth_luma_minus8);
print ("bit_depth_chroma_minus8 = " + bit_depth_chroma_minus8);
print ("qpprime_y_zero_transform_bypass_flag = " + qpprime_y_zero_transform_bypass_flag);
print ("seq_scaling_matrix_present_flag = " + seq_scaling_matrix_present_flag);
print ("log2_max_frame_num_minus4 = " + log2_max_frame_num_minus4);
print ("pic_order_cnt_type = " + pic_order_cnt_type);
print ("log2_max_pic_order_cnt_lsb_minus4 = " + log2_max_pic_order_cnt_lsb_minus4);
print ("max_num_ref_frames = " + max_num_ref_frames);
print ("gaps_in_frame_num_value_allowed_flag = " + gaps_in_frame_num_value_allowed_flag);
print ("pic_width_in_mbs_minus1 = " + pic_width_in_mbs_minus1);
print ("pic_height_in_map_units_minus1 = " + pic_height_in_map_units_minus1);
print ("frame_mbs_only_flag = " + frame_mbs_only_flag);
if (frame_mbs_only_flag == 0) {
print ("mb_adaptive_frame_field_flag = " + mb_adaptive_frame_field_flag);
}
print ("direct_8x8_inference_flag = " + direct_8x8_inference_flag);
print ("frame_cropping_flag = " + frame_cropping_flag);
if (frame_cropping_flag == 1) {
print ("frame_crop_left_offset = " + frame_crop_left_offset);
print ("frame_crop_right_offset = " + frame_crop_right_offset);
print ("frame_crop_top_offset = " + frame_crop_top_offset);
print ("frame_crop_bottom_offset = " + frame_crop_bottom_offset);
}
print ("vui_parameters_present_flag = " + vui_parameters_present_flag);
// ----------------------
print ("------------------------------------");
print ("影片解析度:" + (pic_width_in_mbs_minus1 + 1) * 16 + " x " + (pic_height_in_map_units_minus1 + 1) * 16);
}
unsafe int get_bit_at_position (byte[] buf, int*bytePotion, int*bitPosition)
{
int mask = 0, val = 0;
mask = 1 << (7 - *bitPosition);
val = ((buf [*bytePotion] & mask) != 0) ? 1 : 0;
if (++*bitPosition > 7) {
*bytePotion = *bytePotion + 1;
*bitPosition = 0;
}
return val;
}
unsafe int get_uev_code_num (byte[] buf, int*bytePotion, int*bitPosition)
{
int val = 0, prefixZeroCount = 0;
int prefix = 0, surfix = 0;
while (true) {
val = get_bit_at_position (buf, bytePotion, bitPosition);
if (val == 0) {
prefixZeroCount++;
} else {
break;
}
}
prefix = (1 << prefixZeroCount) - 1;
for (int i = 0; i < prefixZeroCount; i++) {
val = get_bit_at_position (buf, bytePotion, bitPosition);
surfix += val * (1 << (prefixZeroCount - i - 1));
}
prefix += surfix;
return prefix;
}
byte [] GetSPS_ReadFile(string H264_File_Path){
List<byte []> NALU_List = GetNALU_ReadFile (H264_File_Path);
foreach (byte[] bArray in NALU_List) {
if(bArray[0] == 0x67){
byte [] b = new byte[bArray.Length-1];
for(int i=0, k = 1; i < b.Length; i++, k++){
b[i] = bArray[k];
}
return b;
}
}
return null;
}
List<byte []> GetNALU_ReadFile (string H264_File_Path)
{
byte[] b = System.IO.File.ReadAllBytes (H264_File_Path);
List<int> index = new List<int> ();
for (int i = 0; i < b.Length - 3; i++) {
if (b [i] == 0x00 && b [i + 1] == 0x00 && b [i + 2] == 0x01) {
index.Add (i + 3);
}
}
List<byte[]> NALU_List = new List<byte[]> ();
for (int i = 0; i < index.Count - 1; i++) {
int start = index [i];
int end = index [i + 1];
int size = end - start;
byte[] nalu = new byte[size - 3];
for (int x = 0, k = start; k < end - 3; x++, k++) {
nalu [x] = b [k];
}
NALU_List.Add (nalu);
}
return NALU_List;
}
}