开篇
其实,有时候我在怀疑自己的思路到底是否正确,尤其是看到大家都在搞AI,好像只有我一个人在研究如何进行底层应用的开发,但是说实话,这个是我的兴趣所在,而且我认为如果将来做一个健硕的产品,这个是必须要进行的一关,所以我认为还是要继续下去的。
FFMPEG的API编程
如果是希望测试FFMPEG的话,可以直接使用APT进行安装FFMPEG,然后使用CONSOLE进行测试,不过就不要测试他的软编了,不要难为他,硬编和硬解也可以不用测试了,原生的FFMPEG很少支持板子的硬解硬编能力。
对于FFMPEG的API编程来讲,大部分开发板其实是非常困难的,甚至说就算基于X86平台来讲,搭建FFMPEG的API编程也是需要一定的步骤的,但是爱芯派的SDK已经集成了这部分的内容,我看了一下是因为DEMO的需要,不管怎样,对于我们的下一步开始是件非常不错的事情。
在x86上搭建FFMPEG编程环境
其实这步是可以省略的,我这里要先在x86平台上面搭建ffmpeg的编程环境的原因是,为了防止编译后在板子上运行失败,我无法确定是我代码的问题还是编译板子的环境问题,所以,如果只是在板子上面运行,那么这一步是完全可以跳过的。
sudo apt install ffmpeg ffmpeg-doc libavcodec-dev libavcodec-extra libavdevice-dev libavdevice58 libavfilter-dev libavfilter-extra libavformat-dev libavformat-extra libavutil-dev libavutil56 libffmpeg-nvenc-dev libpostproc-dev libpostproc55 libsdl-kitchensink-dev libsdl-kitchensink1 libswresample-dev libswresample3 libswscale-dev libswscale5
直接一行命令就完事了,对了,GCC环境安装了哇?
sudo apt install vim gcc g++ make
基本上有这些就差不多够了。
然后,我们进行编写代码,编写完毕后,就可以编译链接文件了,最后进行运行文件,例如我们的代码文件名是ffmpeg.c,生成的文件是ffmpeg的话:
gcc -g -Wall ffmpeg.c -o ffmpeg -lavformat -lavfilter -lavcodec -lavutil -lswresample -lswscale -lm -lpthread -lrt
其实,如果进行过ffmpeg的api编程的朋友们会发现,或者看过我以前的帖子的朋友们会发现链接库里面少了一些东西,比如avdevice,这个是因为SDK中带的FFMPEG里面没有avdevice的库,所以为了保持和SDK中的相同,所以在这里就不会链接SDK中没有的库了。
可以看到非常丝滑的编译完成了!
编译完成试着运行,也可以看到完全没有问题,报错也没事,在下文贴代码的时候,我会对报错进行解释。
交叉编译
接下来我们进行交叉编译,先贴代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavutil/avutil.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
const char input_link[] = "rtsp://admin:a1234567@192.168.1.65:554/h264/ch1/sub/av_stream";
const char output_link[] = "rtmp://192.168.1.1:8910/rtmplive/cctv";
const int width = 640, height = 480, fps = 10;
int main(int argc, char* argv[]) {
int i_video_output_stream = -1;
int64_t i_video_frame = 0;
avformat_network_init();
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
AVDictionary* options = NULL;
av_dict_set(&options, "buffer_size", "33554432", 0);
av_dict_set(&options, "max_delay", "800000", 0);
av_dict_set(&options, "stimeout", "20000000", 0);
av_dict_set(&options, "rtsp_transport", "tcp", 0);
AVFormatContext *p_video_input_format_ctx = avformat_alloc_context();
AVStream *p_video_input_stream = NULL;
if (avformat_open_input(&p_video_input_format_ctx, input_link, NULL, &options) != 0) {
printf("error: input stream open fail!");
return -1;
}
if (avformat_find_stream_info(p_video_input_format_ctx, NULL) < 0) {
printf("error: couldn't find stream information.\n");
return -1;
}
for (int i = 0; i < p_video_input_format_ctx->nb_streams; i++) {
if (p_video_input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
p_video_input_stream = p_video_input_format_ctx->streams[i];
break;
}
}
if (p_video_input_stream == NULL) {
printf("error: couldn't find video stream.\n");
return -1;
}
printf("----------Video Input Information----------\n");
av_dump_format(p_video_input_format_ctx, 0, NULL, 0);
printf("-------------------------------------------\n");
AVFormatContext *p_output_format_ctx;
if (avformat_alloc_output_context2(&p_output_format_ctx, 0, "flv", output_link) != 0) {
printf("error: avformat_alloc_output_context2!\n");
return -1;
}
AVStream *p_video_output_stream = avformat_new_stream(p_output_format_ctx, NULL);
if (!p_video_output_stream) {
printf("error: avformat_new_stream failed!\n");
return -1;
}
if (avcodec_parameters_copy(p_video_output_stream->codecpar, p_video_input_stream->codecpar) < 0) {
printf("error: avformat_new_stream failed!\n");
return -1;
}
p_video_output_stream->codecpar->codec_tag = 0;
if (avio_open(&p_output_format_ctx->pb, output_link, AVIO_FLAG_WRITE) != 0) {
printf("error: avio_open failed!\n");
return -1;
}
if (avformat_write_header(p_output_format_ctx, NULL) != 0) {
printf("error: avformat_write_header failed!\n");
return -1;
}
for (int i = 0; i<p_output_format_ctx->nb_streams; i++) {
if (p_output_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
i_video_output_stream = i;
}
}
printf("----------Output Information----------\n");
av_dump_format(p_output_format_ctx, 0, output_link, 1);
printf("-------------------------------------------\n");
while (1) {
if (av_read_frame(p_video_input_format_ctx, packet) >= 0){
packet->stream_index = i_video_output_stream;
packet->pts = av_rescale_q_rnd(packet->pts, p_video_input_stream->time_base, p_video_output_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
packet->dts = av_rescale_q_rnd(packet->dts, p_video_input_stream->time_base, p_video_output_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
packet->duration = av_rescale_q(packet->duration, p_video_input_stream->time_base, p_video_output_stream->time_base);
packet->pos = -1;
if (av_interleaved_write_frame(p_output_format_ctx, packet) < 0) {
printf("error: av_interleaved_write_frame failed!\n");
}
av_packet_unref(packet);
i_video_frame++;
}
usleep(10000);
}
return 0;
}
这个代码与之前我在风火轮YY3568之ffmpeg的API编程中贴的代码类似,但是经过一定的改装,例如原代码中是使用的软编码,而在这里是直接转换,即不解码也不编码,就像console中的-vcodec copy一样,然后进行交叉编译,记的代码放在SDK的根目录下,输入命令:
export PATH="/opt/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/:$PATH"
aarch64-none-linux-gnu-gcc -g -Wall ffmpeg.c -o ffmpeg -I third-party/ffmpeg/include -L third-party/ffmpeg/lib -lavformat -lavfilter -lavcodec -lavutil -lswresample -lswscale -lm -lpthread -lrt
如果已经引入变量,第一句是可以不要的哈!
编译这就成功了,没有ERROR,连WARNING都木有!
本来想解释下代码的,但是看了一下,觉的好像没有多大的必要,里面有些以前不用的变量,可以忽略,接下来使用SCP传到板子上试试!
最后运行它!
其实是可以看到最后有一些报错的,但是这些报错并不是代码有问题,包括在X86平台上面也有报错,甚至是使用FFMPEG的CONSOLE命令也会报错,这个是因为HIK的RTSP的DTS和PTS导致的,但是并不会影响我们的转换,只要是不进行编解码都会这样的,所以我们可以忽略,直接看效果吧!
初探PRO的硬编硬解
在官方的sample中其实有关于PRO的编码、解码以及转换的示例程序的,分别是vdec,venc及vdec_ivps_venc,分别对应。暂时没看到官方的API文档,这就不由的说到程序员最讨厌的就是写文档,比这更讨厌的就是别人不写文档,哈哈,而本人的水平也有些次,但可以看出官方的代码很规范,有些类似ffmpeg现在的代码规范,再加上时间有限来不及继续深究了,对于硬解码可以关注一下common下的common_vdec_utils.c中的SampleVdecFfmpegInit和SampleVdecFfmpegExtractOnePic函数,对于编码感兴趣的可以关注common_venc.c下的COMMON_VENC_Start函数,我也肯定会继续研究的,在这里先做一个笔记!
PRO的硬转换
我在上面说了,关于硬转换这块的代码需要关注vdec_ivps_venc这块,大概的含义就是硬解码后保存到ivps然后从ivps中再进行硬编码,这样的处理才可以看出PRO强大的编码能力。就像在X86平台上面,如果解码使用硬解码,编码使用硬编码,那么会发现CPU占用率极低,但是如果解码使用软解码,编码使用硬编码,CPU占用率会变高,甚至比编码也使用软编码的占用率还高,并不是因为解码占用了很多资源,而是因为解码后的YUV帧数据在内存当中,而且还很大,但是需要把数据从内存中复制到显存当中,才可以进行硬编码,所以才导致的占用率较高,所以在实际开发中,有时候明明有硬编码,却还是会选择软编码,当然了在嵌入式中,还是使用硬编码更加合适,但是VDEC link IVPS,然后IVPS link venc才可以看出他真正的能力!