如何将 uint8 I420 视频数据的原始缓冲区拆分为 YUV 指针?

How can I split a raw buffer of uint8 I420 video data into YUV pointers?

我有一个原始 uint8 指针,指向包含 I420 格式视频数据的缓冲区和缓冲区大小。我也知道框架的宽度/高度。我想将数据输入到一个库中,该库可以通过此函数签名创建视频帧:

Copy(int width, int height,
        const uint8_t* data_y, int stride_y,
        const uint8_t* data_u, int stride_u,
        const uint8_t* data_v, int stride_v)

是否有一些简单的指针算法可以解决这个问题?

据我所知,描述各种 YUV 和 RGB 视频格式的最佳网站是 FOURCC - YUV 描述是 here

您所指的 I420 格式已被描述 here。这意味着您的原始数据将像这样组织:

  • Y 平面,H x W 字节,线步幅为 W 字节
  • U 平面,H/2 x W/2 字节,行步幅为 W/2 字节
  • V 平面,H/2 x W/2 字节,行跨度为 W/2 字节

我发现 ffmpeg 是生成 YUV 数据以在您自己的软件中编码和解码的最佳工具。以下是使用 ffmpeg 和原始 YUV 的一些技巧。


您可以获得它支持的像素格式列表:

ffmpeg -pix_fmts

所以,为了找到你的,我找了一些里面有 420 的东西:

ffmpeg -pix_fmts | grep 420p
IO... yuv420p                3             12      8-8-8

所以我知道我需要 -pix_fmt yuv420p 来编码或解码您的数据。我还可以通过查看 ffmpeg 来源 here 获得有关布局方式的体面描述。上面的12表示每像素12位


然后我想:

  • 使用 ffmpeg
  • 生成示例 I420 帧
  • dd将其分开并用IMageMagick
  • 提取Y、U和V通道
  • ImageMagick
  • 重新组合 Y、U 和 V 通道
  • 重新组合 Y、U 和 V 通道 ffmpeg

所以我制作了以下 bash 脚本:

#!/bin/bash

################################################################################
# User-editable values
################################################################################
# Define WIDTH and HEIGHT of the frame we want to generate...
# ... so we are consistent all the way through
W=640
H=480
PIX_FMT="yuv420p"

################################################################################
# Derived values - do not edit
################################################################################
BASENAME="${PIX_FMT}-${W}x${H}"
FILENAME="${BASENAME}.raw"
PNGNAME="${BASENAME}.png"
UVW=$((W/2))           # width of U plane, same as V plane
UVH=$((H/2))           # height of U plane, same as V plane
YBYTES=$((H*W))        # bytes in Y plane
UBYTES=$((UVW*UVH))    # bytes in U plane, same as in V plane

# Generate a sample frame
echo "Generating sample: ${FILENAME}, and viewable PNG equivalent: ${PNGNAME}"
ffmpeg -y -f lavfi -i testsrc=size=${W}x${H}:rate=1:duration=1 -vcodec rawvideo -pix_fmt "$PIX_FMT" -f image2pipe -  > "$FILENAME"
ffmpeg -y -f lavfi -i testsrc=size=${W}x${H}:rate=1:duration=1 "$PNGNAME"

# Check its size in bytes
ls -l "$FILENAME"

# Extract Y plane from sample into "Y.png" using ImageMagick
echo "Extracting Y plane into Y.png"
dd if="$FILENAME" bs=$YBYTES count=1 | magick -depth 8 -size ${W}x${H} gray:- Y.png

# Extract U plane from sample into "U.png" using ImageMagick
echo "Extracting U plane into U.png"
dd if="$FILENAME" bs=1 skip=$YBYTES count=$UBYTES | magick -depth 8 -size ${UVW}x${UVH} gray:- U.png

# Extract V plane from sample into "V.png" using ImageMagick
echo "Extracting V plane into V.png"
dd if="$FILENAME" bs=1 skip=$((YBYTES+UBYTES)) count=$UBYTES | magick -depth 8 -size ${UVW}x${UVH} gray:- V.png

# Recombine with ImageMagick
echo "Combining Y.png, U.png, V.png into result.png"
magick Y.png \( U.png v.png -resize 200% \) -set colorspace YUV -combine result.png

# Create a PNG from the YUV420p raw data just the same with 'ffmpeg'
echo "Create PNG from the YUV420p raw data as 'extracted.png'" 
ffmpeg -y -f rawvideo -video_size 640x480 -pixel_format yuv420p -i - extracted.png < "$FILENAME"

将此图像创建为 PNG 和 I420 数据供您测试:

以及这些 Y、U 和 V 平面: