视频格式与编码格式太混乱 一文搞定视频编码(附转码命令)

344 阅读6分钟

小剧场

公司有个项目,大概就是车辆报警时候启动摄像头,拍摄一段不是很长的视频,然后上传云端,在用户查看的时候可以通过浏览器或者app播放。一切都是这样愉快而简单。

半个月后,车端的同事给我直接丢过来一个stream_0.h265文件

我 :“这是什么?”

同事:“上传播放的视频”

我:“h265是什么格式”

同事:“不知道,大概是视频吧”

我:“能转成mp4这种大家都认识的格式吗”

同事:“不能”

我:“所以这个文件怎么导出来的”

同事:“供应商提供了摄像头驱动,照着示例代码写的”

我:“你这段视频没问题吧?”

同事:“不知道,我没看,我又打不开,应该没问题”

我:“… …”

事后,我花时间了解了视频编码的事情,对h265有了理解,我知晓这种方式压缩率大,省带宽,但是编码比较费CPU和内存。而摄像头是供车内自驾功能使用的,走内网,对cpu可能更加敏感。

于是我不由得怀有最大恶意去想,一个视频设备的供应商,大概也许不会只提供一种编码,尤其编码部分和硬件无关,开源方案那么成熟。可是,谁知道呢,毕竟同事很忙。

容器编码(视频格式) & 编码解码器

在一般人的认知中,存在这一个常见的误区,那就是认为视频文件的后缀名就是视频的格式,但事实并不是那样的。

视频文件的后缀实际上含义是“封装格式/容器/容器编码(Container),容器编码里面包含了视频播放所需的各种文件,这些文件包括:

  • 视频流(Video Stream)
  • 音频流(Audio Stream)
  • 元数据(Meta Data) 也称为“描述数据的数据”,它里面包含了视频的信息,例如码率、分辨率、字幕、设备信息和制作时间等一些内容

mp4-水印版.png

其中,元数据里面最重要的一部分就是编码解码器Codec),Codec也有很多种,每种编码格式都对应一种Codec,编码格式还分为视频编码格式音频编码格式,固Codec也有视频Codec音频Codec

常见的视频codec

  • H.264(AVC)
  • H.265(HEVC)
  • VP9
  • AV1

常见的音频codec

  • MP3
  • AAC
  • AC-3

当然除了上面提到的MP4,还有其他的视频格式,例如,AVI、MKV、MOV、m3u8

MP4

大家常说的MP4,更具体应该指MPEG-4,更更具体应该称MPEG-4 part14

因为MPEG-4这个名称很容易私人迷惑,因为不同的人叫MPEG-4时,所指的含义可能会不一样。常见的3种含义分别是:

  • MP4 容器(MPEG-4 part 14,正在讨论的是这个)
  • ISO Base Media File(MPEG-4 part 12,在视频流上用到)
  • H.264 Codec(MPEG-4 part 10,在视频压制上用到)

MP4是一种对于Web程序输出视频友好的容器编码,因为它存在单独的一个容器中并且得到各种各样的设备和操作系统支持。MP4只是一种容器格式,可以容纳使用不同编码标准(如H.264、H.265等)进行压缩的视频编码解码。

H.264是目前为止使用得最普遍Codec,所以要保证视频兼容性,建议使用 MP4 + H.264 。例如目前大部分浏览器都支持H.264格式解码,但是不一定支持H.265格式解码。

H.264和H.265

H.264(AVC,Advanced Video Coding)和H.265(HEVC,High Efficiency Video Coding)是广泛使用的视频编码标准。

  • H.264和H.265都采用高效的视频压缩算法,以降低数据传输和存储的需求。
  • H.264编码是一种先进的视频编码技术,已成为现代视频传输和存储的主流标准。
  • H.265编码在保持视频质量的同时,进一步提高了压缩效率(压缩率几乎是H.264的两倍),但是编解码几乎消耗3倍的资源

识别视频文件格式

使用ffmpeg -i参数可以识别出视频的格式

$ ffmpeg -i stream_0.h265 
​
... ...
​
[hevc @ 0x55a067999600] Stream #0: not enough frames to estimate rate; consider increasing probesize
Input #0, hevc, from 'stream_0.h265':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Video: hevc (Main), yuv420p(tv), 2880x1856, 30 fps, 30 tbr, 1200k tbn, 30 tbc
At least one output file must be specified

可以发现,同事发给我的是一个hevc视频,并没有容器编码,对比之下,mp4格式文件的信息如下

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output.h264.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:00:20.90, start: 0.000000, bitrate: 7441 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 2880x1856, 7437 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
    Metadata:
      handler_name    : VideoHandler

格式转换

所以很明确,只需要转换一下编码,将“裸露”的视频包上容器,这样浏览器就可以直接播放了,这里选择的是 h.264 + mp4

opencv

首先想到的是通过opencv进行转码,具体代码如下

import cv2
​
​
def convert_h265_to_h264(input_file, output_file):
    # 打开H.265视频文件
    video_capture = cv2.VideoCapture(input_file)
​
    # 获取原始视频的宽度、高度和帧率
    width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = video_capture.get(cv2.CAP_PROP_FPS)
​
    # 创建输出视频编码器
    fourcc = cv2.VideoWriter_fourcc(*'h264')
    # fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video_writer = cv2.VideoWriter(output_file, fourcc, fps, (width, height))
​
    while True:
        # 读取一帧
        ret, frame = video_capture.read()
​
        if not ret:
            break
​
        # 写入转码后的H.264帧到输出视频文件
        video_writer.write(frame)
​
        # cv2.imshow("capture", frame)  # 显示
        # if cv2.waitKey(1) & 0xff == ord('q'):  # 按q退出
        #     break
​
    # 释放资源
    video_capture.release()
    # video_writer.release()
​
​
if __name__ == '__main__':
    convert_h265_to_h264("stream_0.h265", 'output.h264.mp4')

最后发现读视频是没有问题的,但是一帧一帧写为mp4就是有问题的,应该是opencv的编译问题

但是opencv是调用ffmpeg实现的视频编解码,而我电脑上又装有ffmpeg,所以可以直接用

ffmpeg

命令如下

ffmpeg -i stream_0.h265 -c:v libx264 output.h264.mp4

python调用代码如下

def convert_h265_to_h264(input_file, output_file):
    # 使用FFmpeg进行转码
    subprocess.call(['ffmpeg', '-i', input_file, '-c:v', 'libx264', output_file])
    """
    -i:指定输入文件的路径。
    -c:v:指定视频编码器。在此示例中,我们使用libx264作为H.264编码器。
​
    subprocess.call()是Python标准库中的一个函数,用于执行外部命令。
    它会在调用外部命令时阻塞当前线程,直到外部命令执行完成。如果需要保持应用程序的响应性,可以考虑使用subprocess.Popen()或异步操作来执行外部命令。
    """

注意,格式转换要花费大量的时间,生产使用需要仔细考虑资源的占用问题