image - 从 MPEG-2 传输流 (H.264 - Annex B) 字节流中提取原始 I 帧图像数据

标签 image rust frame h.264 mpeg

上下文

我正在尝试使用 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!

概念验证

到目前为止,我已经在 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%。

例如:

Ouput jpg image

问题

  1. 是否需要执行解码步骤?
  2. 我是否正确地处理了这个问题,自己尝试是否可行,或者我应该依赖另一个库?

如有任何帮助,我们将不胜感激!

最佳答案

“是否需要执行解码步骤?”

是的。从头开始编写解码器非常复杂。描述它的文档 (ISO 14496-10) 超过 750 页。你应该使用图书馆。来自 ffmpeg 的 Libavcodec 确实是您唯一的选择。 (除非你只需要baseline profile,里面可以使用android的开源解码器)

您可以编译自定义版本的 libavcodec 以排除不需要的内容。

关于image - 从 MPEG-2 传输流 (H.264 - Annex B) 字节流中提取原始 I 帧图像数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60547654/

相关文章:

rust - 如何使用退出代码干净地结束程序?

rust - AssertUnwindSafe 如何与 CatchUnwind future 一起使用

ios - 在 iOS 5.0+ 中获取捕获视频的第一帧

Python,我如何获得gif帧

javascript - JavaScript 中的图像链接

android - 如何从图像中获取信息?

python - 如何找到图像中所有矩形瓷砖?

c# - 从几个重叠的 png 中创建一个 jpeg

optimization - 如何防止 Rust 基准库优化我的代码?

java - java中有限制组件只能在Frame中移动的功能吗?