从 MPEG-2 传输流 (H.264 - Annex B) 字节流中提取原始 I 帧图像数据
Extract raw I frame image data from MPEG-2 Transport Stream (H.264 - Annex B) byte stream
上下文
我正在尝试使用 H.264 附件 B 编解码器从 MPEG-2 传输流中提取每个 I 帧的原始图像数据。此视频包含每 2 秒间隔的 I 帧。我读过 I 帧可以在类型为 5 的 NALu 起始代码之后找到(例如,IDR 图片的编码切片)。这些 NALu 的字节有效载荷包含构建完整帧所需的所有数据。尽管,据我所知,采用 H.264 编码格式。
我想构建一个解决方案来从传入的字节流中提取这些 I 帧,方法是找到包含 I 帧的 NALu,保存有效负载并将有效负载解码为一些普遍存在的原始图像格式以访问像素数据等等
Note: I would like to avoid using filesystem dependency binaries like ffmpeg if possible and more importantly if feasible!
PoC
到目前为止,我已经在 Rust 中构建了一个 PoC 来查找 I 帧的字节偏移量和字节大小:
use std::fs::File;
use std::io::{prelude::*, BufReader};
extern crate image;
fn main() {
let file = File::open("vodpart-0.ts").unwrap();
let reader = BufReader::new(file);
let mut idr_payload = Vec::<u8>::new();
let mut total_idr_frame_count = 0;
let mut is_idr_payload = false;
let mut is_nalu_type_code = false;
let mut start_code_vec = Vec::<u8>::new();
for (pos, byte_result) in reader.bytes().enumerate() {
let byte = byte_result.unwrap();
if is_nalu_type_code {
is_idr_payload = false;
is_nalu_type_code = false;
start_code_vec.clear();
if byte == 101 {
is_idr_payload = true;
total_idr_frame_count += 1;
println!("Found IDR picture at byte offset {}", pos);
}
continue;
}
if is_idr_payload {
idr_payload.push(byte);
}
if byte == 0 {
start_code_vec.push(byte);
continue;
}
if byte == 1 && start_code_vec.len() >= 2 {
if is_idr_payload {
let payload = idr_payload.len() - start_code_vec.len() + 1;
println!("Previous NALu payload is {} bytes long\n", payload);
save_image(&idr_payload.as_slice(), total_idr_frame_count);
idr_payload.clear();
}
is_nalu_type_code = true;
continue;
}
start_code_vec.clear();
}
println!();
println!("total i frame count: {}", total_idr_frame_count);
println!();
println!("done!");
}
fn save_image(buffer: &[u8], index: u16) {
let image_name = format!("image-{}.jpg", index);
image::save_buffer(image_name, buffer, 858, 480, image::ColorType::Rgb8).unwrap()
}
结果如下:
Found IDR picture at byte offset 870
Previous NALu payload is 202929 bytes long
Found IDR picture at byte offset 1699826
Previous NALu payload is 185069 bytes long
Found IDR picture at byte offset 3268686
Previous NALu payload is 145218 bytes long
Found IDR picture at byte offset 4898270
Previous NALu payload is 106114 bytes long
Found IDR picture at byte offset 6482358
Previous NALu payload is 185638 bytes long
total i frame count: 5
done!
这是正确的,根据我使用 H.264 位流查看器等进行的研究。在这些字节偏移处肯定有 5 个 I 帧!
问题是我不明白如何将 H.264 字节流负载转换为原始图像 RBG 数据格式。转换为 jpg 后生成的图像只是一团模糊的东西,大约占图像区域的 10%。
例如:
问题
- 是否需要执行解码步骤?
- 我是否正确地处理了这个问题,自己尝试是否可行,或者我应该依赖另一个库?
如有任何帮助,我们将不胜感激!
“是否需要执行解码步骤?”
是的。从头开始编写解码器非常复杂。描述它的文档 (ISO 14496-10) 超过 750 页。你应该使用图书馆。来自 ffmpeg 的 Libavcodec 确实是您唯一的选择。 (除非你只需要基线配置文件,其中你可以使用来自 android 的开源解码器)
您可以编译自定义版本的 libavcodec 以排除不需要的内容。
上下文
我正在尝试使用 H.264 附件 B 编解码器从 MPEG-2 传输流中提取每个 I 帧的原始图像数据。此视频包含每 2 秒间隔的 I 帧。我读过 I 帧可以在类型为 5 的 NALu 起始代码之后找到(例如,IDR 图片的编码切片)。这些 NALu 的字节有效载荷包含构建完整帧所需的所有数据。尽管,据我所知,采用 H.264 编码格式。
我想构建一个解决方案来从传入的字节流中提取这些 I 帧,方法是找到包含 I 帧的 NALu,保存有效负载并将有效负载解码为一些普遍存在的原始图像格式以访问像素数据等等
Note: I would like to avoid using filesystem dependency binaries like ffmpeg if possible and more importantly if feasible!
PoC
到目前为止,我已经在 Rust 中构建了一个 PoC 来查找 I 帧的字节偏移量和字节大小:
use std::fs::File;
use std::io::{prelude::*, BufReader};
extern crate image;
fn main() {
let file = File::open("vodpart-0.ts").unwrap();
let reader = BufReader::new(file);
let mut idr_payload = Vec::<u8>::new();
let mut total_idr_frame_count = 0;
let mut is_idr_payload = false;
let mut is_nalu_type_code = false;
let mut start_code_vec = Vec::<u8>::new();
for (pos, byte_result) in reader.bytes().enumerate() {
let byte = byte_result.unwrap();
if is_nalu_type_code {
is_idr_payload = false;
is_nalu_type_code = false;
start_code_vec.clear();
if byte == 101 {
is_idr_payload = true;
total_idr_frame_count += 1;
println!("Found IDR picture at byte offset {}", pos);
}
continue;
}
if is_idr_payload {
idr_payload.push(byte);
}
if byte == 0 {
start_code_vec.push(byte);
continue;
}
if byte == 1 && start_code_vec.len() >= 2 {
if is_idr_payload {
let payload = idr_payload.len() - start_code_vec.len() + 1;
println!("Previous NALu payload is {} bytes long\n", payload);
save_image(&idr_payload.as_slice(), total_idr_frame_count);
idr_payload.clear();
}
is_nalu_type_code = true;
continue;
}
start_code_vec.clear();
}
println!();
println!("total i frame count: {}", total_idr_frame_count);
println!();
println!("done!");
}
fn save_image(buffer: &[u8], index: u16) {
let image_name = format!("image-{}.jpg", index);
image::save_buffer(image_name, buffer, 858, 480, image::ColorType::Rgb8).unwrap()
}
结果如下:
Found IDR picture at byte offset 870
Previous NALu payload is 202929 bytes long
Found IDR picture at byte offset 1699826
Previous NALu payload is 185069 bytes long
Found IDR picture at byte offset 3268686
Previous NALu payload is 145218 bytes long
Found IDR picture at byte offset 4898270
Previous NALu payload is 106114 bytes long
Found IDR picture at byte offset 6482358
Previous NALu payload is 185638 bytes long
total i frame count: 5
done!
这是正确的,根据我使用 H.264 位流查看器等进行的研究。在这些字节偏移处肯定有 5 个 I 帧!
问题是我不明白如何将 H.264 字节流负载转换为原始图像 RBG 数据格式。转换为 jpg 后生成的图像只是一团模糊的东西,大约占图像区域的 10%。
例如:
问题
- 是否需要执行解码步骤?
- 我是否正确地处理了这个问题,自己尝试是否可行,或者我应该依赖另一个库?
如有任何帮助,我们将不胜感激!
“是否需要执行解码步骤?”
是的。从头开始编写解码器非常复杂。描述它的文档 (ISO 14496-10) 超过 750 页。你应该使用图书馆。来自 ffmpeg 的 Libavcodec 确实是您唯一的选择。 (除非你只需要基线配置文件,其中你可以使用来自 android 的开源解码器)
您可以编译自定义版本的 libavcodec 以排除不需要的内容。