【问题标题】:Extract raw I frame image data from MPEG-2 Transport Stream (H.264 - Annex B) byte stream从 MPEG-2 传输流(H.264 - 附件 B)字节流中提取原始 I 帧图像数据
【发布时间】:2020-03-05 14:30:09
【问题描述】:

上下文

我正在尝试使用 H.264 附件 B 编解码器从 MPEG-2 传输流中提取每个 I 帧的原始图像数据。该视频包含每 2 秒间隔的 I 帧。我读过可以在类型为 5 的 NALu 开始代码之后找到 I 帧(例如 IDR 图片的编码切片)。这些 NALu 的字节有效载荷包含构建完整帧所需的所有数据。尽管我的理解是采用 H.264 编码格式。

我想构建一个解决方案来从传入的字节流中提取这些 I 帧,方法是找到包含 I 帧的 NALu,保存有效负载并将有效负载解码为一些普遍存在的原始图像格式以访问像素数据等。

注意:如果可能,我想避免使用像 ffmpeg 这样的文件系统依赖二进制文件,如果可行,更重要的是!

概念验证

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

例如:

问题

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

任何帮助将不胜感激!

【问题讨论】:

    标签: image rust frame h.264 mpeg


    【解决方案1】:

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

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

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

    【讨论】:

    • 感谢您的澄清!我已经看到了一个关于 ISO 文档大小的类似问题警告的答案。追问:I-frame NALu payload解码后,图像数据会以什么格式表示?像 libavcodec 这样的库是否能够只接受单帧字节有效负载?
    • "像 libavcodec 这样的库是否能够只接受单帧字节有效负载?"这取决于格式,如果它的附件B和一个“框架”包括一个SPS/PPS和IDR,那么是的。
    • "I-frame NALu payload解码后,图像数据会以什么格式表示?"取决于编码的内容,但最常见的是具有 4:2:0 色度二次采样平面的 YUV。
    猜你喜欢
    • 2010-11-24
    • 2013-09-15
    • 1970-01-01
    • 2014-07-24
    • 1970-01-01
    • 2012-04-27
    • 2014-06-22
    • 2014-05-06
    • 2020-05-26
    相关资源
    最近更新 更多