Извлечение необработанных данных изображения кадра I из потока байтов транспортного потока MPEG-2 (H.264 - Приложение B)

Контекст

Я пытаюсь извлечь необработанные данные изображения для каждого I-кадра из транспортного потока MPEG-2 с кодеком приложения B H.264. Это видео содержит I-кадры через каждые 2 секунды. Я читал, что I-кадр можно найти после начального кода NALu с типом 5 (например, закодированный фрагмент изображения IDR). Полезная нагрузка байтов этих NALU содержит все необходимые данные для создания полного кадра. Хотя, насколько я понимаю, в формате с кодировкой H.264.

Я хотел бы создать решение для извлечения этих I-кадров из входящего потока байтов, находя NALu, которые содержат I-кадры, сохраняя полезную нагрузку и декодируя полезную нагрузку в какой-то вездесущий необработанный формат изображения для доступа к данным пикселей и т. д.

Примечание. Я хотел бы избегать использования бинарных зависимостей файловой системы, таких как ffmpeg, если это возможно и, что более важно, если это возможно!

PoC

До сих пор я построил 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% площади изображения.

Например:

Вывод jpg-изображения

Вопросы

  1. Есть ли шаг декодирования, который необходимо выполнить?
  2. Правильно ли я подхожу к этому, и можно ли попробовать самому, или мне следует полагаться на другую библиотеку?

Любая помощь будет принята с благодарностью!


person Jarvis Prestidge    schedule 05.03.2020    source источник


Ответы (1)


«Есть ли шаг декодирования, который необходимо выполнить?»

да. А написать декодер с нуля ЧРЕЗВЫЧАЙНО сложно. Описывающий его документ (ISO 14496-10) занимает более 750 страниц. Вы должны использовать библиотеку. Libavcodec из ffmpeg действительно ваш единственный вариант. (Если вам не нужен только базовый профиль, в котором вы можете использовать декодер с открытым исходным кодом от андроида)

Вы можете скомпилировать собственную версию libavcodec, чтобы исключить то, что вам не нужно.

person szatmary    schedule 05.03.2020
comment
Спасибо за разъяснение! Я видел ответ на аналогичный вопрос с предупреждением о размере документа ISO. Последующие вопросы: после декодирования полезной нагрузки I-кадра NALu, в каком формате будут представлены данные изображения? И сможет ли такая библиотека, как libavcodec, принимать полезную нагрузку только одного байта кадра? - person Jarvis Prestidge; 05.03.2020
comment
И сможет ли такая библиотека, как libavcodec, принимать полезную нагрузку только одного байта кадра? Это зависит от формата. Если его приложение B и кадр включают SPS/PPS и IDR, то да. - person szatmary; 05.03.2020
comment
После декодирования полезной нагрузки I-кадра NAlu, в каком формате будут представлены данные изображения? Зависит от того, в чем он был закодирован, но наиболее распространенным является YUV с планировщиком субдискретизации цветности 4: 2: 0. - person szatmary; 05.03.2020