基于SIP协议音视频通讯终端的测试:从工具选择到pyav的探索

原因

基于SIP协议音视频通讯终端的测试:从工具选择到pyav的探索

该公司的产品是基于SIP协议的音频和视频通信终端。最新产品需要执行音频和视频测试。在以前的工具中,仅支持音频返回测试。后来,执行了视频循环函数。最近的会议还谈论了音频和视频测试的测试。使用不同的编解码器进行测试以完成测试。我觉得我以前的循环不能用作良好的测试条件(因为它也涉及产品本身的音频和视频收集),因此有如此需要。

探索

实际上,在开始时,我没有选择使用ffmpeg,而是选择使用我忘了致电的库。无论如何它都不容易使用,也是基于C的库。后来,我要求使用ffmeg,所以我找到了ffmpeg-python和python-ffmeg。这两个库均基于FFMPEG-CLI,并且操作粒度不是很好。我搜索并找到了Pyav。

Pyav是用Cython编写的图书馆。它重新包装ffmeg对象用于Python。

但是,这些文件极为罕见,中国文件非常小。因此,只需通过触摸石头就过河。

需要

我的需要是打开本地文件,然后通过RTP流的形式通过本地指定端口将其发送到目标地址的目标端口。

有必要通过两个不同的端口同时发送视频和音频。

演示过程中使用的音频编解码器是PCMU,使用的视频编解码器为H264。

思维

在音频方面,因为我想达到20ms的ptime,time.sleep()无法在Windows系统下的毫秒内等待,因此需要在Linux系统中运行。为了实现更准确的20ms间隔软件包,它也需要经过专门设计。检查时间。

我在程序启动之前录制了一个standard_start,并在解码新框架之前使用了标准_start +20ms,将其用作标准解码启动时间,然后记录了解码之前的时间,而startup delays_time_time = trays_start -stant_start -standard_start是要计算的。 time.time() - 真正_start作为编解码器完成后使用的时间,因此剩余时间应为20ms -delay_time-如果大于零,等待,并且如果它小于零,则将立即进入下一个帧。代码解码过程。

在视频方面,因为编码器具有40架缓冲区(我不知道在哪里设置它,但这是正确的),所以您不应该等待您在编码器队列中的每个帧,但您应该等待等待编码器输出。开始等待。然后,在末尾,缓冲区队列中的40帧将由out_packets = self.output_stream.encode()取出,然后按下流。

演示

伪代码可能是:

//打开输入文件容器

input_ = av.open(in_file)

//打开输出容器

输出= av.open(rtp_url)

//添加输出流

output_stream = output.add_stream(编解码)

//解码输入框架

input_frame = input.decode(音频= 0)

//编码输出包

output_packets = output_stream.encode(input_frame)

//输出包

output.mux(output_packets)

class AVPlayer:
    def __init__(self, use_port: int, res: str = 'test_30fps_8000hz.avi'):
        self.remote_ip = ''
        self.remote_port = None
        self.use_port = use_port
        self.res = os.path.join(os.path.dirname(__file__), res)
        self._input = av.open(self.res, 'r')
        self.url = ''
        self.url_option = ''
        self.codec = ''
        self.rate = None
        self._output = None
        self.output_stream = None
        self.resampler = None
        self.is_config = False
        self.is_sending = False
class MyAudioPlayer(AVPlayer):
    def __init__(self, use_port, res: str = 'test_30fps_8000hz.avi'):
        super().__init__(use_port, res)
        self.channels = None
        self.format = None
        self.ptime = None
        self.pkt_size = None
    def config(self, aim_ip, aim_port, codec: str = 'pcmu', ptime: int = 20):
        if codec == 'pcmu':
            self.codec = 'pcm_mulaw'
            self.rate = 8000
            self.channels = 1
        elif codec == 'pcma':
            self.codec = 'pcm_alaw'
            self.rate = 8000
            self.channels = 1
        else:
            raise TypeError
        if self.channels == 1:
            self.format = "mono"
        else:
            self.format = "stereo"
        self.ptime = ptime
        self.pkt_size = self.channels * self.rate * self.ptime / 1000
        self.url_option = f'&pkt_size={self.pkt_size + 12}'
        self.url = f'rtp://{aim_ip}:{aim_port}?localrtpport={self.use_port}{self.url_option}'
        self._output = av.open(self.url, 'w', format="rtp")
        self.output_stream = self._output.add_stream(self.codec, self.rate)
        self.output_stream.codec_context.channels = self.channels
        self.resampler = AudioResampler("s16", self.format, self.rate, self.pkt_size)
        self.is_config = True
        self.is_sending = True
        thread = threading.Thread(target=self._send)
        thread.setDaemon(True)
        thread.start()
    def _send(self):
        standard_start = time.time()  # 标准启动时
        pts = 0
        while self.is_sending:
            with av.open(self.res, 'r') as _input:
                for input_frame in _input.decode(audio=0):
                    resample_frames = self.resampler.resample(input_frame)
                    for resample_frame in resample_frames:
                        out_packets = self.output_stream.encode(resample_frame)
                        if out_packets:
                            for out_packet in out_packets:
                                out_packet.pts = pts
                                out_packet.dts = pts
                                really_start = time.time()  # 实际启动时
                                delay_time = really_start - standard_start  # 启动偏差
                                self._output.mux(out_packet)
                                used = time.time() - really_start  # 实际用时
                                ptime = (self.ptime / 1000)  # 标准用时
                                if (delay_time + used) < ptime:
                                    time.sleep(ptime - used - delay_time)
                                standard_start += ptime
                                pts += 160
            _input.close()
class MyVideoPlayer(AVPlayer):
    def __init__(self, use_port, res: str = 'test_30fps_noB_30gop_8000hz_20ms.avi'):
        super().__init__(use_port, res)
        self.stream_option = {}
        self.pack_mode = None
        self.option = {}
    def config(self, aim_ip, aim_port, codec: str = 'h264', pack_mode: int = 1):
        if codec == 'h264':
            self.codec = 'libx264'
            self.rate = 30
            self.stream_option = {"crf": "25"}
        else:
            raise TypeError
        if pack_mode == 0:
            self.url_option = '&pkt_size=65535'
            self.pack_mode = pack_mode
            self.option = {"rtpflags": "h264_mode0"}
        else:
            self.url_option = ''
            self.pack_mode = pack_mode
            self.option = {}
        self.url = f'rtp://{aim_ip}:{aim_port}?localrtpport={self.use_port}{self.url_option}'
        self._output = av.open(self.url, "w", format="rtp", options=self.option)
        self.output_stream = self._output.add_stream(self.codec, self.rate, options=self.stream_option)
        self.output_stream.width = self._input.streams.video[0].codec_context.width
        self.output_stream.height = self._input.streams.video[0].codec_context.height
        self.output_stream.pix_fmt = self._input.streams.video[0].codec_context.pix_fmt
        self._input.close()
        self.is_config = True
        self.is_sending = True
        thread = threading.Thread(target=self._send)
        thread.setDaemon(True)
        thread.start()
    def _send(self):
        standard_start = time.time()  # 标准启动时
        pts = 0
        while self.is_sending:
            with av.open(self.res, 'r') as _input:
                for input_frame in _input.decode(video=0):
                    pts += 1
                    input_frame.pts = pts
                    out_packets = self.output_stream.encode(input_frame)
                    if out_packets:
                        for out_packet in out_packets:
                            out_packet.dts = out_packet.pts
                            really_start = time.time()  # 实际启动时
                            delay_time = really_start - standard_start  # 启动偏差
                            self._output.mux(out_packet)
                            used = time.time() - really_start  # 实际用时
                            ptime = 1 / self.rate  # 标准用时
                            if (delay_time + used) < ptime:
                                time.sleep(ptime - used - delay_time)
                            standard_start += ptime
                    if not self.is_sending:
                        break
        out_packets = self.output_stream.encode()
        for out_packet in out_packets:
            out_packet.dts = out_packet.pts
            really_start = time.time()  # 实际启动时
            delay_time = really_start - standard_start  # 启动偏差
            self._output.mux(out_packet)
            used = time.time() - really_start  # 实际用时
            ptime = 1 / self.rate  # 标准用时
            if (delay_time + used) < ptime:
                time.sleep(ptime - used - delay_time)
            standard_start += ptime
class Media:
    def __init__(self):
        self.res = os.path.join(os.path.dirname(__file__), 'test_30fps_8000hz.avi')
        self.aim_ip = '10.20.0.13'
        self.aim_a_port = 12100
        self.aim_v_port = 12150
        self.use_a_port = 12100
        self.use_v_port = 12150
        self.audio_codec = ''
        self.audio_player = MyAudioPlayer(self.use_a_port)
        self.video_player = MyVideoPlayer(self.use_v_port)
    def send_all(self):
        self.audio_player.config(self.aim_ip, self.aim_a_port)
        self.video_player.config(self.aim_ip, self.aim_v_port)

你可能想看:
« 上一篇
下一篇 »

相关推荐

python爬虫教程PDF,Python爬虫书PDF

2026年09月02日

31阅读

外国教程python,python编程教学视频

2026年09月02日

25阅读

python简明教程沈洁元,python详细基础教程

2026年09月02日

21阅读

python库包教程,python 包

2026年09月02日

26阅读

python教程大全集,python教程600集

2026年09月02日

26阅读

python入门教程完整版,python零基础入门全套教程视频

2026年09月02日

22阅读