FFMPEG Api 从 YUV420P 到 RGB 的转换产生奇怪的输出

FFMPEG Api conversion from YUV420P to RGB produces strange output

我在 Rust 中使用 FFMPEG Api 从视频文件中获取 RGB 图像。

虽然有些视频工作正常并且我按预期恢复了帧,但有些视频不工作。或者至少结果不是我预期的那样。

我在 Rust 中使用的代码:

ffmpeg::init().unwrap();

let in_ctx = input(&Path::new(source)).unwrap();
let input = in_ctx
    .streams()
    .best(Type::Video)
    .ok_or(ffmpeg::Error::StreamNotFound)?;

let decoder = input.codec().decoder().video()?;

let scaler = Context::get(
    decoder.format(),
    decoder.width(),
    decoder.height(),
    Pixel::RGB24,
    decoder.width(),
    decoder.height(),
    Flags::FULL_CHR_H_INT | Flags::ACCURATE_RND,
)?; // <--- Is basically sws_getContext

// later to get the actual frame
let mut decoded = Video::empty();
if self.decoder.receive_frame(&mut decoded).is_ok() {
    let mut rgb_frame = Video::empty();
    self.scaler.run(&decoded, &mut rgb_frame)?; // <--- Does sws_scale
    println!("Converted Pixel Format: {}", rgb_frame.format() as i32);
    Ok(Some(rgb_frame))
}

应该大致翻译成 C 语言:

// Get the context and video stream
SwsContext * ctx = sws_getContext(imgWidth, imgHeight,
                              imgFormat, imgWidth, imgHeight,
                              AV_PIX_FMT_RGB24, 0, 0, 0, 0);
sws_scale(ctx, decoded.data, decoded.linesize, 0, decoded.height, rgb_frame.data, rbg_frame.linesize);

就像我之前说的,有时它工作正常并且我得到了预期的帧。但有时我会得到这样的东西: Weird result image

我将图像保存为 .ppm 文件以便快速进行视觉比较。我使用了这种方法,它基本上将字节写入一个简单的文件 .ppm header:

fn save_file(frame: &Video, index: usize) -> std::result::Result<(), std::io::Error> 
{
    let mut file = File::create(format!("frame{}.ppm", index))?;
    file.write_all(format!("P6\n{} {}\n255\n", frame.width(), frame.height()).as_bytes())?;
    file.write_all(frame.data(0))?;
    Ok(())
}

在这里您可以看到左侧的图像结果较好,而右侧的图像结果较差。 Comparison of the .ppm files

现在来回答问题:

为什么会这样。我测试了我这边的一切,唯一剩下的就是 ffmpeg 转换。 FFMPEG 似乎以不同的方式转换这两个测试文件,即使它报告 YUV420P 作为两种格式。我不知道有什么区别...

这里是我使用的两个视频文件的信息:

好的视频文件:

General
Complete name                            : /mnt/smb/Snapchat-174933781.mp4
Format                                   : MPEG-4
Format profile                           : Base Media / Version 2
Codec ID                                 : mp42 (isom/mp42)
File size                                : 1.90 MiB
Duration                                 : 9 s 612 ms
Overall bit rate                         : 1 661 kb/s
Encoded date                             : UTC 2021-07-28 22:09:36
Tagged date                              : UTC 2021-07-28 22:09:36
eng                                      : -180.00

Video
ID                                       : 512
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : High@L3.1
Format settings                          : CABAC / 1 Ref Frames
Format settings, CABAC                   : Yes
Format settings, Reference frames        : 1 frame
Format settings, GOP                     : M=1, N=30
Codec ID                                 : avc1
Codec ID/Info                            : Advanced Video Coding
Duration                                 : 9 s 598 ms
Bit rate                                 : 1 597 kb/s
Width                                    : 480 pixels
Height                                   : 944 pixels
Display aspect ratio                     : 0.508
Frame rate mode                          : Variable
Frame rate                               : 29.797 FPS
Minimum frame rate                       : 15.000 FPS
Maximum frame rate                       : 30.000 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.118
Stream size                              : 1.83 MiB (96%)
Title                                    : Snap Video
Language                                 : English
Encoded date                             : UTC 2021-07-28 22:09:36
Tagged date                              : UTC 2021-07-28 22:09:36
Color range                              : Full
colour_range_Original                    : Limited
Color primaries                          : BT.709
Transfer characteristics                 : BT.601
transfer_characteristics_Original        : BT.709
Matrix coefficients                      : BT.709
Codec configuration box                  : avcC

Audio
ID                                       : 256
Format                                   : AAC LC
Format/Info                              : Advanced Audio Codec Low Complexity
Codec ID                                 : mp4a-40-2
Duration                                 : 9 s 612 ms
Bit rate mode                            : Constant
Bit rate                                 : 62.0 kb/s
Channel(s)                               : 1 channel
Channel layout                           : C
Sampling rate                            : 44.1 kHz
Frame rate                               : 43.066 FPS (1024 SPF)
Compression mode                         : Lossy
Stream size                              : 73.3 KiB (4%)
Title                                    : Snap Audio
Language                                 : English
Encoded date                             : UTC 2021-07-28 22:09:36
Tagged date                              : UTC 2021-07-28 22:09:36

错误的视频文件:

General
Complete name                            : /mnt/smb/Snapchat-1989594918.mp4
Format                                   : MPEG-4
Format profile                           : Base Media / Version 2
Codec ID                                 : mp42 (isom/mp42)
File size                                : 2.97 MiB
Duration                                 : 6 s 313 ms
Overall bit rate                         : 3 948 kb/s
Encoded date                             : UTC 2019-07-11 06:43:04
Tagged date                              : UTC 2019-07-11 06:43:04
com.android.version                      : 9

Video
ID                                       : 1
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : Baseline@L3.1
Format settings                          : 1 Ref Frames
Format settings, CABAC                   : No
Format settings, Reference frames        : 1 frame
Format settings, GOP                     : M=1, N=30
Codec ID                                 : avc1
Codec ID/Info                            : Advanced Video Coding
Duration                                 : 6 s 313 ms
Bit rate                                 : 3 945 kb/s
Width                                    : 496 pixels
Height                                   : 960 pixels
Display aspect ratio                     : 0.517
Frame rate mode                          : Variable
Frame rate                               : 29.306 FPS
Minimum frame rate                       : 19.767 FPS
Maximum frame rate                       : 39.508 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.283
Stream size                              : 2.97 MiB (100%)
Title                                    : VideoHandle
Language                                 : English
Encoded date                             : UTC 2019-07-11 06:43:04
Tagged date                              : UTC 2019-07-11 06:43:04
Color range                              : Limited
Color primaries                          : BT.709
Transfer characteristics                 : BT.709
Matrix coefficients                      : BT.709
Codec configuration box                  : avcC

或作为差异图像:image diff

问题是我对 ffmpeg 不是很熟悉,但我不知道它的所有怪癖。

我希望有人能指出我正确的方向。

感谢 @SuRGeoNix and @Jmb 的建议,我调整了输入的线条大小和宽度。

过了一会儿,我了解到 ffmpeg 需要 32 位对齐的数据才能实现最佳性能。所以我调整了缩放器以缩放到 32 位对齐宽度,现在输出很好。

ffmpeg::init().unwrap();

let in_ctx = input(&Path::new(source)).unwrap();
let input = in_ctx
    .streams()
    .best(Type::Video)
    .ok_or(ffmpeg::Error::StreamNotFound)?;

let decoder = input.codec().decoder().video()?;

// Round to the next 32bit divisible width
let width = if decoder.width() % 32 != 0 {
    decoder.width() + 32 - (decoder.width() % 32)
} else {
    decoder.width()
};

let scaler = Context::get(
    decoder.format(),
    decoder.width(),
    decoder.height(),
    Pixel::RGB24,
    width, // Use the calculated width here
    decoder.height(),
    Flags::FULL_CHR_H_INT | Flags::ACCURATE_RND,
)?;