瑞芯微Rockchip开发者社区
直播中

dutong0321

3年用户 732经验值
擅长:模拟技术 嵌入式技术 接口/总线/驱动 光电显示 控制/MCU RF/无线
私信 关注
[经验]

【风火轮YY3568开发板免费体验】04.FFMPEG API编程

在上一篇,我们已经成功使用FFMPEG通过命令行的方式进行了RTMP的直播,其实FFMPEG还可以通过API的方式进行编程,并且使用API的方式,那么拥有着更多的灵活性,这也是为什么要自编译ffmpeg,而不是直接使用APT命令安装的方式来运行它。我们最终的目标是使用RKMPP来将视频进行编码,然后通过FFMPEG发送到RTMP服务器上进行直播,那么方案有两种,一种就是修改FFMPEG源码进行编译,第二种方式就只能通过API编程来进行了。
那么这一篇的任务有两方面:
1.因为我们要使用ffmpeg推RTMP流,那么我们先不继承RKMPP,先从RTSP进行拉流,然后使用软解软编后进行推流,使用纯C编写。
2.在Linux下面的软件编译,gcc hello.c -o hello一般只用来编译hello world,但是在实际编译过程中我们常常需要引入其他的库来进行编译,所以需要增加一些参数。
废话就说到这里吧,上代码:

#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 "libavdevice/avdevice.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.66:554/h264/ch1/sub/av_stream";
const char output_link[] = "rtmp://192.168.1.103:8910/rtmplive/cctv";
const int width = 640, height = 480, fps = 25;

int main(int argc, char* argv[]) {
	int	i_video_output_stream = -1;
	int64_t i_video_frame = 0;
	
	avformat_network_init();
	avdevice_register_all();
	
	AVFrame *p_frame = av_frame_alloc();
	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);  //tcp方式打开
	
	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;
	}
	const AVCodec *p_video_input_codec = avcodec_find_decoder(p_video_input_stream->codecpar->codec_id);
	AVCodecContext *p_video_input_codec_ctx = avcodec_alloc_context3(p_video_input_codec);
	if (avcodec_open2(p_video_input_codec_ctx, p_video_input_codec, NULL) < 0) {
		printf("error: couldn't open codec.\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;
	}
	const AVCodec *p_video_output_codec = avcodec_find_encoder_by_name("libx264");
	if (!p_video_output_codec){
		printf("error: couldn't find encoder!\n");
		return -1;
	}
	AVCodecContext *p_video_output_codec_ctx = avcodec_alloc_context3(p_video_output_codec);
	if (!p_video_output_codec_ctx) {
		printf("error: avcodec_alloc_context3 failed!\n");
		return -1;
	}
	p_video_output_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数
	p_video_output_codec_ctx->codec_id = p_video_output_codec->id;
	p_video_output_codec_ctx->bit_rate = 512 * 1024;//比特率
	p_video_output_codec_ctx->width = width;
	p_video_output_codec_ctx->height = height;
	p_video_output_codec_ctx->time_base.num = 1;
	p_video_output_codec_ctx->time_base.den = fps;
	p_video_output_codec_ctx->framerate.num = fps;
	p_video_output_codec_ctx->framerate.den = 1;
	p_video_output_codec_ctx->gop_size = 50;
	p_video_output_codec_ctx->max_b_frames = 0;
	p_video_output_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	// Set H264 preset and tune
	AVDictionary *param = 0;
	if (p_video_output_codec_ctx->codec_id == AV_CODEC_ID_H264) {
		// 这个非常重要,如果不设置延时非常的大 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo 是x264编码速度的选项
		av_dict_set(&param, "preset", "superfast", 0);
		av_dict_set(&param, "tune", "zerolatency", 0);
	}
	if (avcodec_open2(p_video_output_codec_ctx, p_video_output_codec, &param) < 0){
		printf("error: couldn't open encoder!\n");
		return -1;
	}
	//添加视频流
	AVStream *p_video_output_stream = avformat_new_stream(p_output_format_ctx, p_video_output_codec);
	if (!p_video_output_stream) {
		printf("error: avformat_new_stream failed!\n");
		return -1;
	}
	//附加标志,这个一定要设置
	p_video_output_stream->codecpar->codec_tag = 0;
	//从编码器复制参数
	avcodec_parameters_from_context(p_video_output_stream->codecpar, p_video_output_codec_ctx);

	///打开rtmp 的网络输出IO  AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
	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");

	//int iVideoFrameSize = avpicture_get_size(pVideoOutputCodecCtx->pix_fmt, pVideoOutputCodecCtx->width, pVideoOutputCodecCtx->height);
	//uint8_t *pPictureBuf = new uint8_t[iVideoFrameSize];
	int video_duration = (p_output_format_ctx->streams[i_video_output_stream]->time_base.den / p_output_format_ctx->streams[i_video_output_stream]->time_base.num) / fps;
	while (1) {
		if (av_read_frame(p_video_input_format_ctx, packet) >= 0){
			if (avcodec_send_packet(p_video_input_codec_ctx, packet) < 0 || avcodec_receive_frame(p_video_input_codec_ctx, p_frame) < 0) {
				printf("error: decode video failed!\n");
				continue;
			}
			av_packet_unref(packet);
			packet = av_packet_alloc();
			//packet->data = NULL;
			//packet->size = 0;
			
			if (avcodec_send_frame(p_video_output_codec_ctx, p_frame) != 0) {
				printf("error: send frame failed!\n");
				continue;
			}
			av_frame_unref(p_frame);
				
			if (avcodec_receive_packet(p_video_output_codec_ctx, packet) != 0 || packet->size <= 0) {
				printf("error: avcodec_receive_packet failed!\n");
				continue;
			}
			packet->stream_index = i_video_output_stream;
			packet->pts = i_video_frame * video_duration;
			packet->dts = i_video_frame * video_duration;
			packet->duration = video_duration;

			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++;
			av_frame_unref(p_frame);
			av_packet_unref(packet);
		}
		usleep(10000);
	}
	
	return 0;
}

然后,我们进行编译,编译的命令放在下面,注意的是链接库最好按照我的顺序来,不然可能会出问题的。
image.png

可以看到,甚至连一个警告都没有,完美!
接下来我们就开始运行吧:
103.png
运行到这里可以看到我们让输出input信息和output信息还有编码器的信息已经成功输出了,但是输出后就好像卡死了,但是其实是它在正常工作,咱们没有继续让他进行输出啊,所以如果希望改进的话,你可以让他增加一些统计信息,但是可以50帧左右,也就是2秒输出一下就可以了,一方面这样看起来整洁容易看出问题,另外一方面也可以减轻CPU的负担。
104.png
使用top命令看一下CPU的使用率,不过才76%,如果满载的话将会是400%,可以说是即使是软编,那么也处理640x480的视频很轻松了。
107.png
实际测试效果图如上,为了隐私我打上了马赛克,不过可以看到时间的。
106.png
视频信息如上,可以看到软件已经推流成功了,完美!

说明

  1. 在这个程序中直接把链接写死在程序里了,其实相对来讲,我更建议大家通过启动时通过命令行后面的参数进行传入,在程序里直接就可以通过argv就可以进行读取了,这个是支持的,我已经写好在main里面了。
  2. 这个程序是可以读取本地文件,也可以生成本地文件,还可以读本地文件推流RTMP,也可以读RTSP生成本地文件,例如要生成本地文件的话,可以将代码中的第20行修改为:const char output_link[] = "cctv.flv";.
  3. 本程序只做了视频部分,因为视频部分比较占用CPU资源,音频部分占用小,而且视频部分代码比较简单好理解,后期增加音频流也基本一致,但是需要注意的有可能frame和packet并不是一一对应的,发送出去以后一定要用while来收回,还有就是注意时间轴的同步。
  4. 本程序使用的是纯C来进行编写的,主要是程序比较小,而且ffmpeg也是使用纯C写的,可以看到文件也都写在了一起,如果做的功能比较多,还是建议使用C++使用面向对象来完成,引入头文件时记的添加extern "C",而且要用到多线程,包括视频解码线程,音频解码线程以及编码线程等等。
  5. 要使用音视频的缓存AVFifoBuffer
  6. 增加QT界面,同时最好使用QT的项目管理,这样不用每次都输入那么多的参数了。如果不适用QT,不做界面,那么建议使用Makefile来管理项目。

更多回帖

发帖
×
20
完善资料,
赚取积分