如何提取YUV文件的特定帧

从YUV文件中提取出特定的某一帧有哪几种方式?

这种场合一般发生在需要帧同步调试/特别对比某两帧/查看内容/批处理的场合,不过其实专门讨论意义有限。因为往往都有图形化界面自由查看。

因此这其实是一个相对无聊的问题。当然,换一种方式也可以:对于像YUV这样一个规整的格式,有多少种工具可以拆分文件?

p.s. 以下都以YUV420格式 为例子。其每帧大小为 <math xmlns="http://www.w3.org/1998/Math/MathML"> W × H × 3 2 W\times H \times \frac{3}{2} </math>W×H×23.

FFmpeg

  • 如果手头有FFmpeg工具,这是坠吼滴。
css 复制代码
ffmpeg -s [width]x[height] -i input.yuv -vframes 1 -f rawvideo -pix_fmt yuv420p output.yuv

-vframes 1 表示只处理一个视频帧。

  • 视场景设置,改换为帧率指定也是可以的。

例如,对于30fps的视频,提取第10秒的一帧。即第300帧:

css 复制代码
ffmpeg -s [width]x[height] -r 30 -i input.yuv -ss 00:00:10 -vframes 1 -f rawvideo -pix_fmt yuv420p output.yuv

Python

高级语言就任意折腾就好。一般都是系统之一的模块。

python 复制代码
width = 1920  # 示例宽度
height = 1080  # 示例高度
frame_number = 1  # 提取第1帧
​
# YUV420格式一帧数据大小
frame_size = width * height + (width // 2) * (height // 2) * 2
​
with open('input.yuv', 'rb') as file:
    file.seek(frame_size * (frame_number - 1))
    frame_data = file.read(frame_size)
    with open(f'output_{width}x{height}.yuv', 'wb') as output_file:
        output_file.write(frame_data)

C

同上,高级语言任意折腾。常规 File IO.

ini 复制代码
#include <stdio.h>
#include <stdlib.h>
​
int main()
{
    FILE *file = fopen("input.yuv", "rb");
    if (file == NULL)
    {
        perror("Error opening file");
        return 1;
    }
​
    int width = 1920;     // 宽度
    int height = 1080;    // 高度
    int frame_number = 1; // 提取的帧号
​
    // YUV420格式一帧数据大小
    int frame_size = width * height + (width / 2) * (height / 2) * 2;
    unsigned char *frame_data = (unsigned char *)malloc(frame_size);
    if (frame_data == NULL)
    {
        perror("Memory allocation failed");
        fclose(file);
        return 1;
    }
​
    fseek(file, (long)frame_size * (frame_number - 1), SEEK_SET);
    fread(frame_data, 1, frame_size, file);
    FILE *output_file = fopen("output.yuv", "wb");
    if (output_file != NULL)
    {
        fwrite(frame_data, 1, frame_size, output_file);
        fclose(output_file);
    }
    else
    {
        perror("Error opening output file");
    }
    free(frame_data);
    fclose(file);
    return 0;
}
​

Command line

其实主要是想折腾这种方式,即在没有FFmpeg和高级语言支持下怎么(顺手)完成规整文件读写这样的小事。

dd

可以考虑dd,直接指定文件读写的位置和大小。

例如,1920x1080 YUV 420 提取第 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i帧:

bash 复制代码
dd if=input.yuv of=output_frame.yuv bs=1 count=$((1920*1080*3/2)) skip=$(( [i-1] *1920*1080*3/2))

其中, -if=input.yuv 指定输入文件; -of=output_frame.yuv 指定输出文件; -bs=1 设置块大小为1字节; -count=$((1920*1080*3/2)) 指定复制的字节数,即一帧的大小; -skip=$((4*1920*1080*3/2)) 跳过前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i − 1 i-1 </math>i−1帧的数据量,以提取第 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i帧.

split & cat

或者直接使用split分帧split分帧是确实方便,就是文件命名比较折腾)

bash 复制代码
# 分帧,以frame_xx命名,xx遵循字母计数法。
split -b $((1920*1080*3/2)) input.yuv frame_
# 例如,将某一帧(_aa,26x25 帧范围内转换出来的第一帧)转换为输出文件
cat frame_aa > output.yuv

当然,因为split 命令生成的文件后缀是字母计数法 。是按照字母顺序递增的,从 aa 开始,然后是 ab,以此类推。所以会遗留一堆不需要的分帧。

解决这个问题的话可以通过一个粗糙的shell脚本,其编写了convert_number_to_suffix函数,实现了最简单粗暴的字母计数法转换(ASCII码直转),可以处理帧数为26~676的视频。

bash 复制代码
#!/bin/bash
​
# 字母计数法和阿拉伯数字转换脚本,范围为 26~676(即26x26)
convert_number_to_suffix() {
    local num=$1
    local -i charcode_a=97
    local -i first_digit=$(( (num - 1) / 26 + charcode_a ))
    local -i second_digit=$(( (num - 1) % 26 + charcode_a ))
    local first_char=$(printf \$(printf '%03o' $first_digit) )
    local second_char=$(printf \$(printf '%03o' $second_digit) )
    echo "$first_char$second_char"
}
if [ "$#" -ne 4 ]; then
    echo "Usage: $0 <input.yuv> <width> <height> <frame_number>"
    exit 1
fi
# 输入的YUV文件路径
INPUT_FILE=$1
# 视频的宽度
WIDTH=$2
# 视频的高度
HEIGHT=$3
# 提取的帧序号
FRAME_NUMBER=$4
​
FRAME_SIZE=$((WIDTH * HEIGHT * 3 / 2))
FILE_SUFFIX=$(convert_number_to_suffix $FRAME_NUMBER)
split -b $FRAME_SIZE $INPUT_FILE frame_
cat "frame_$FILE_SUFFIX" > "output_frame_$FRAME_NUMBER.yuv"
​
# 清理其他分割文件
rm frame_*
​

与其说是在折腾分帧,不如说是在折腾命名方法

总结

已经折腾得简单问题复杂化了。

TODO:

  • 对比高级语言脚本实现与dd/cat方式的性能分析
  • 字母序列计数法的实现讨论,我们能实现一个更完善的版本(当然,那会更复杂,而且属于另一个问题了)
相关推荐
x007xyz1 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥1 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
涵小呆2 个月前
AV1技术学习:Constrained Directional Enhancement Filter
视频编码·av1
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播
哔哩哔哩技术2 个月前
自研点直播转码核心
音视频开发
音视频牛哥2 个月前
Android平台轻量级RTSP服务模块二次封装版调用说明
音视频开发·视频编码·直播
涵小呆2 个月前
AV1技术学习:Loop Restoration Filter
视频编码·av1
音视频牛哥2 个月前
Android平台RTSP|RTMP直播播放器技术接入说明
音视频开发·视频编码·直播
涵小呆2 个月前
AV1技术学习:Transform Coding
视频编码·av1