专业的编程技术博客社区

网站首页 > 博客文章 正文

FFmpeg使用小结

baijin 2024-11-28 07:59:37 博客文章 4 ℃ 0 评论

最近由于工作需要在做一些视频监控相关的工作,其中一个研究点就是如何将网络摄像头的实时视频流拉取并保存到本地,我使用的开发语言是python,通过查找资料找到了如下几种方法。

  1. 使用opencv进行操作。使用opencv读取网络摄像头有一篇比较好的文章,由于本文主要介绍FFmpeg的使用,所以在这里不展开介绍。
  2. 使用FFmpeg进行操作。以下是从《FFmpeg从入门到精通》中引用的一段介绍:

FFmpeg既是一款音视频编解码工具,同时也是一组音视频编码开发套件,作为编解码开发套件,它为开发者提供了丰富的音视频处理的调用接口。

相较于opencv,FFmpeg对视频的处理更为专业,提供的接口也更加丰富,使得我们开发起来更加方便,对程序流程的掌控也更加精细。在接下来的篇幅中,我将从以下几个部分对FFmpeg的使用进行介绍。


视频文件的相关概念

我们都知道,视频播放其实就是时空上连续的一帧一帧图片按照某一切换速率进行显示,由于人眼的暂留效应,当这个切换速率高于某一值后,人眼将无法察觉出相邻两帧之间的切换,整个画面就“动起来了”。而这个切换速率,就是视频中常说的帧率。

帧率:视频1s内展示了多少张图片。

但是,如果我们对一个视频中的所有帧都无压缩的保存,那么一个视频的大小将非常大,以1080P的视频为例,一般1080P的视频每一帧大小为1920*1080,每一帧3个通道,每个像素用uint8存储,则每一帧的大小为6MB左右,假设是一部1小时的电影,帧率为25,则整部电影文件的大小将达到529GB。所以为了能将视频在保证画质的前提下减小视频的大小,一些压缩算法被应用到了视频中,而这些压缩算法就是视频中的编码格式。视频压缩的基本前提就是相邻帧之间其实变化并不是很大,所以当我们保存某一帧后,其实只需要保存相邻帧对其的变化即可。这就是视频压缩中的帧间压缩,还有一种压缩是帧内压缩,其实就是图片的压缩。

视频使用的压缩编码格式有MPEG4,MJPEG,AAC,H.264,H.265(HEVC)等,其中H.264是目前使用最为广泛的压缩编码。

除了编码格式外,视频中的另外一个概念是封装格式,所谓封装格式,可以简单看成是一个容器,里面除了视频以外,还可以包括音频、字幕等信息。我们常见的视频文件后缀MP4就是一种封装格式,除了MP4以外,还有FLV、TS、KV等文件封装格式,还有RTMP、RTSP、HLS等网络协议封装格式。

所以,一般我们对一个视频进行操作时,常常需要经过以下一个处理流程。

原视频->解封装->packet->解码->frame->编码->packet->封装->目标视频。

在这些操作中,编解码的资源和时间开销相对是比较大的,而封装解封装的开销比较小,所以,如果仅仅是需要转换封装格式,就不要编解码了,而opencv没法做到这一点,它只能获取到帧。

相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

音视频免费学习地址:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

FFmpeg使用方法

    1. 直接使用FFmpeg命令行进行操作。

可以直接在代码中使用subprocess.popen()(非阻塞),或者subprocess.run()(阻塞)来调用命令行命令。当操作比较简单时可以使用这种方法。

2. 使用FFmpeg api(PyAV)接口进行操作。接下来会给出一些操作示例,由于作者给出的文档实在过于简略,笔者也是经过多次摸索,参看了项目issue以及一些视频相关资料之后才最终实现。

#rtsp转封装为多个mp4文件
import av
import os


class RTSP2MP4(object):
    def __init__(self, rtsp_addr):
        self.output_container = None
        self.output_stream = None
        self.input_container = av.open(rtsp_addr, timeout=3,
                                       options={'rtsp_transport': 'tcp', 'pix_fmt': 'yuv420p', 'fflags': 'nobuffer'})
        self.input_stream = self.input_container.streams.video[0]
        self.file_idx = 0
        self.save_path = ''
        self.duration=300 # 每5分钟保存一个文件
        self.rescaling_pts = None # 每个文件的第一帧pts都必须为0,否则播放器播放会出现异常,因此需要记录每一个文件第一帧在rtsp视频流中的pts值

    def save_run(self):
        first_packet = True

        for packet in self.input_container.demux(self.input_stream):
            if not packet.is_keyframe:  # 保存文件时,如果起始帧不是关键帧,则后续得P帧没有参考,保存的视频会花屏
                continue
            if first_packet:
                self.rescaling_pts = packet.pts
                first_packet = False
                self.create_output_container(os.path.join(self.save_path, f'{self.file_idx}.mp4'), packet)
                self.file_idx += 1
            else:
                if (packet.pts - self.rescaling_pts) * packet.time_base >= self.duration and packet.is_keyframe:
                    self.close_save_video()
                    self.create_output_container(os.path.join(self.save_path, f'{self.file_idx}.mp4'), packet)
                    self.file_idx += 1
                    self.rescaling_pts = packet.pts

            packet.pts -= self.rescaling_pts
            packet.dts -= self.rescaling_pts
            self.output_container.mux(packet)

    def create_output_container(self, save_file_path, packet):
        self.output_container = av.open(save_file_path, mode='w', format='mp4')
        self.output_stream = self.output_container.add_stream(template=packet.stream)

    def close_save_video(self):
        if self.output_container is not None:
            self.output_container.close()
            self.output_container = None

if __name__ == '__main__':
    transfer=RTSP2MP4()
    transfer.save_run()


#rtsp转码为mp4

import av
import os


class RTSP2MP4(object):
    def __init__(self, rtsp_addr):
        self.output_container = None
        self.output_stream = None
        self.input_container = av.open(rtsp_addr, timeout=3,
                                       options={'rtsp_transport': 'tcp', 'pix_fmt': 'yuv420p', 'fflags': 'nobuffer'})
        self.input_stream = self.input_container.streams.video[0]
        self.file_idx = 0
        self.save_path = ''
        self.duration = 300  # 每5分钟保存一个文件
        self.rescaling_pts = None  # 每个文件的第一帧pts都必须为0,否则播放器播放会出现异常,因此需要记录每一个文件第一帧在rtsp视频流中的pts值

    def save_run(self):
        first_packet = True

        for frame in self.input_container.decode(self.input_stream):
            if first_packet:
                self.rescaling_pts = frame.pts
                first_packet = False
                self.create_output_container(os.path.join(self.save_path, f'{self.file_idx}.mp4'), frame)
                self.file_idx += 1
            else:
                if (frame.pts - self.rescaling_pts) * frame.time_base >= self.duration and frame.is_keyframe:
                    self.close_save_video()
                    self.create_output_container(os.path.join(self.save_path, f'{self.file_idx}.mp4'), frame)
                    self.file_idx += 1
                    self.rescaling_pts = frame.pts

            # 由于重新设置了编码参数,因此需要将pts和time_base都设置为None,由ffmpeg根据编码参数自行决定这两个值的
            frame.pts = None
            frame.time_base = None
            self.output_container.mux(self.output_stream.encode(frame))

    def create_output_container(self, save_file_path, frame):
        self.output_container = av.open(save_file_path, mode='w', format='mp4')
        self.output_stream = self.output_container.add_stream('h264', rate=frame.base_rate)
        self.output_stream.options = {'profile': 'High', 'crf': '28', 'preset': 'veryfast', 'threads': '1'}
        self.output_stream.width = frame.stream.width
        self.output_stream.height = frame.steram.height

    def close_save_video(self):
        if self.output_container is not None:
            # flush操作
            self.output_container.mux(self.output_stream.encode(None))
            self.output_container.close()
            self.output_container = None


if __name__ == '__main__':
    transfer = RTSP2MP4()
    transfer.save_run()


注意事项:

FFmpeg压缩

即使同样使用H264编码,还有三种不同的压缩方法,可以通过-profile指定。如果没指定默认使用的是high参数,压缩比会比较好。

-preset使用very fast参数压缩比最高,速度也相对较快,推荐。

-crf 调整码率,默认为23,0为无损压缩。如果想无损保存视频,最好使用-qp 0。

当-vcodec设置为copy时,所有可能更改视频质量、压缩比的参数都将无效,如上面的-crf -qp,也就是说只有转码的时候(-vcodec不是copy模式),设置的参数才有效。

原文 https://zhuanlan.zhihu.com/p/370106433

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表