美文网首页编程实践通信与流媒体
Asterisk播放mp4(3)——搭建开发环境

Asterisk播放mp4(3)——搭建开发环境

作者: 全栈顾问 | 来源:发表于2020-08-13 17:27 被阅读0次

    Asterisk现有版本不支持播放视频文件(支持视频通话),无法满足发送视频通知、视频IVR等场景。本系列文章,通过学习音视频的相关知识和工具,尝试实现一个通过Asterisk播放mp4视频文件的应用。

    Asterisk是一个开源SIP应用服务器,支持多种方式进行扩展。本文介绍如何利用Docker搭建Asterisk的本地开发测试环境,并且提供一些利用docker简化开发过程的技巧。因为本系列文章要通过ffmpeg提供媒体处理能力,所以会涉及如何整合Asterisk和ffmpeg的内容。

    项目地址:https://github.com/jasony62/tms-asterisk-dev

    准备Docker环境

    下载需要版本的asterisk-13.33.0源代码压缩包,官网下载地址

    建立Dockerfile文件,制作基于centos7环境的镜像。为了跳过ffmpeg的安装(从源代码安装ffmpeg真的很麻烦),以jrottenberg/ffmpeg:4.2.3-centos7为基础开始构建。

    在镜像构建阶段完成asterisk的编译和安装,建立build-asterisk.sh文件将编译和安装过程从Dockerfile中独立出来。执行过程中以menuselect命令行方式指定安装选项。

    安装过程中需要使用第三方库janssonpjproject,会在编译阶段进行下载,但是因为网络问题(https://raw.githubusercontent.com能否正常访问)会下载失败,导致无法安装。为了解决这个,先把这两个包下载到本地。然后修改asterisk源代码包中的third-party目录中这个两个项目的Makefile.rules文件(用环境变量HOST_DOMAIN指定主机地址,因为是在容器中运行的,不能写成localhost127.0.0.1),将下载文件的地址指向本地。为了实现在本地下载文件,执行npm i http-server -g安装http服务器(如果需要),在存放下载文件的目录下,执行http-server -p 80启动文件下载服务。

    我们要在自定义的asterisk应用中使用ffmpeg,因此需要修改编译选项。在源代码目录中打开main/Makefile,添加需要的动态链接库。(注意:这里修改后需要重新构建镜像。)

    AST_LIBS+=-lavformat -lavcodec -lavutil -lswresample -lswscale -lavfilter
    

    做完上述准备工作就可以开始构建镜像,运行容器了。

    • 构建镜像

    docker-compose -f docker-compose.13.yml build

    • 启动容器

    docker-compose -f docker-compose.13.yml up

    • 进入容器

    docker exec -it tms-asterisk_13.33.0 bash

    • 结束容器(启动后复制到容器中的内容会丢失,如果直接ctrl+c关闭会保留)

    docker-compose -f docker-compose.13.yml down

    设置Asterisk

    Asterisk支持通过配置文件定制系统的行为,例如:使用extensions.conf指定拨号计划等。在docker-compose.13.yml文件中,将本地配置文件和容器中的文件建立了关联,这样修改本地文件后,在容器中重启asterisk服务就可以了。

    另外,在开发测试时经常需要查看日志,因此通过docker-compose.13.yml文件,将asterisk的日志目录指向了本地,便于查看日志输出内容。

    Asterisk处理媒体时需要动态提供RTP端口,因为docker在windows和mac环境中不支持设置为host模式,需要在docker-compose.13.yml文件中指定开放的端口(和配置文件rtp.conf中的设置保持一致。)。

    Asterisk运行在容器中,其地址无法访问,这样会导致媒体协商中指定的地址无法访问,需要在pjsip.conf文件中将external_media_addressexternal_signaling_address指定为宿主机地址。

    Asterisk常用命令

    • 重启asterisk。不需要用命令asterisk -r进入asterisk后再执行core restart now,两步变一步。

    asterisk -rx "core restart now"

    • 截断日志。asterisk输出的日志非常多,为了便于查看经常需要在输出前清空之前的日志。

    asterisk -rx "logger rotate"

    • 打开pjsip的日志。

    asterisk -rx "pjsip set logger on"

    开发应用

    docker-compose.13.yml文件中,通过volumns指令将自定义应用文件关联到Asterisk源代码的apps目录下(/usr/src/asterisk/apps)。每次在外部修改代码后,到容器中执行make && make install,重启asterisk。

    Asterisk在代码中提供了三种输出日志ast_logast_verbast_debug的方法,通过配置文件logger.conf进行设置。可以将通过配置将不同的信息(类型和等级)输出到不同文件。ast_verb支持设置level控制输出的内容,可以在启动时指定asterisk -vvv,也可以通过命令指定core set verboseast_debug支持设置level控制输出的内容,可以在启动时指定asterisk -ddd,也可以通过命令指定core set debuglevel的设置没有限制。

    播放alaw裸流文件

    • 设置帧类型和采样编码格式
    f->frametype = AST_FRAME_VOICE;
    f->subclass.format = ast_format_alaw;
    
    • 设置采样数据
    f->samples = nb_samples;
    uint8_t *data;
    data = AST_FRAME_GET_BUFFER(f);
    memcpy(data, output_data, nb_samples);
    f->datalen = nb_samples;
    
    • 帧间添加延时。填不填有什么影响?
    duration = (int)(((float)nb_samples / (float)ALAW_SAMPLE_RATE) * 1000 * 1000);
    ast_debug(2, "完成第 %d 个RTP帧发送,添加延时 %d\n", nb_rtps, duration);
    usleep(duration);
    
    • 对比wireshark抓包数据和alaw文件中的数据
    sine-8k-10s.alaw wireshark抓包

    可以看到数据是一致的,说明我们通过asterisk正确地发送了数据。

    播放mp3文件

    Asterisk不支持mp3编码,我们通过ffmpeg进行转码。

    音频处理流程

    ffmpeg是一个媒体编处理框架,整合非常多的编解码库,并且对媒体加工过程进行了抽象,形成了一个统一的处理流程。我理解,处理流程大体上分为5个阶段:

    1. 初始化阶段。读取文件中的信息,准备好编码器,解码器,重采样器等。
    2. 解码阶段。从文件中读取编码包(AVPacket),将编码包发送解码器,从解码器中接收未压缩的原始帧(AVFrame)。
    3. 重采样阶段。每种音频编码格式的原始帧采样格式(sample_fmt)不一样,例如:mp3的采样格式为fltp,alaw的采样格式为s16(我理解,采样格式是没有压缩的采样数据的记录方式,alaw对应的是13位数据,所以没有压缩的状态就需要用s16记录)。为了进行编码转换,首先需要编码格式的转换,这个过程叫做重采样。
    4. 编码阶段。将采样数据帧(AVFrame)发送给编码器,从编码器中获得编码后的包(AVPacket),通过astersik发送rtp包。
    5. 清理阶段。释放内存。

    按照上一篇文章中对mp3文件格式的解析,可以知道sine-8k-10s.mp3文件中包含141帧,第1帧是Info,所以数据帧有140帧。数据帧的帧头为ffe3 18c4,可知,每个数据帧包含576个采样,每帧72字节(8kbps/8kHz*576/8=72)。

    app_tms_mp3输出的日志:

    读取编码包 #1 size= 72 字节
    读取编码包 #1 前8个字节 ff e3 18 c4 00 0d 20 96
    读取编码包 #2 size= 72 字节
    读取编码包 #2 前8个字节 ff e3 18 c4 06 0e 70 f2
    读取编码包 #3 size= 72 字节
    读取编码包 #3 前8个字节 ff e3 18 c4 07 0d e8 a6

    从音频包 #2 中读取音频帧 #1, format = fltp , sample_rate = 8000 , channels = 1 , nb_samples = 47, pts = 1016064, best_effort_timestamp = 1016064

    从音频包 #3 中读取音频帧 #2, format = fltp , sample_rate = 8000 , channels = 1 , nb_samples = 576, pts = 2032128, best_effort_timestamp = 2032128
    结束播放文件 /var/lib/asterisk/media/sine-8k-10s.mp3,共读取 141 个包,共 10152 字节,共生成 140 个包,共 80000 字节,共发送RTP包 140 个,采样 80000 个,耗时 10207260

    sine-8k-10s.mp3

    通过对比程序输出的日志和样本文件中的数据,可以看到ffmpeg通过av_read_frame读取的包,就是mp3文件中的帧,是文件中原始的未解压数据。mp3文件中每一帧的大小虽然一样(72字节),但是包含的采样数并不相同(第1包有27个,后续的包都是576个)。通过编码后,生成80000个采样,大小是80000个字节,和预期一致。

    遗留问题

    在asterisk中如何知道对端支持的编码格式?如果在ast_frame中指定了和用户端不一致的编码格式,asterisk会进行转码?

    解码mp3文件时,AVFrame中包含pts,如何理解和使用这个数据?可以替代通过采样数计算发送间隔吗?

    参考

    Asterisk PBX Docker image

    Using Menuselect to Select Asterisk Options

    Logging Configuration

    Logging

    Running Asterisk

    相关文章

      网友评论

        本文标题:Asterisk播放mp4(3)——搭建开发环境

        本文链接:https://www.haomeiwen.com/subject/vkbvrktx.html