在上一篇,我们已经成功使用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);
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;
AVDictionary *param = 0;
if (p_video_output_codec_ctx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "superfast", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
}
if (avcodec_open2(p_video_output_codec_ctx, p_video_output_codec, ¶m) < 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);
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 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();
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;
}
然后,我们进行编译,编译的命令放在下面,注意的是链接库最好按照我的顺序来,不然可能会出问题的。
可以看到,甚至连一个警告都没有,完美!
接下来我们就开始运行吧:
运行到这里可以看到我们让输出input信息和output信息还有编码器的信息已经成功输出了,但是输出后就好像卡死了,但是其实是它在正常工作,咱们没有继续让他进行输出啊,所以如果希望改进的话,你可以让他增加一些统计信息,但是可以50帧左右,也就是2秒输出一下就可以了,一方面这样看起来整洁容易看出问题,另外一方面也可以减轻CPU的负担。
使用top命令看一下CPU的使用率,不过才76%,如果满载的话将会是400%,可以说是即使是软编,那么也处理640x480的视频很轻松了。
实际测试效果图如上,为了隐私我打上了马赛克,不过可以看到时间的。
视频信息如上,可以看到软件已经推流成功了,完美!
说明
- 在这个程序中直接把链接写死在程序里了,其实相对来讲,我更建议大家通过启动时通过命令行后面的参数进行传入,在程序里直接就可以通过argv就可以进行读取了,这个是支持的,我已经写好在main里面了。
- 这个程序是可以读取本地文件,也可以生成本地文件,还可以读本地文件推流RTMP,也可以读RTSP生成本地文件,例如要生成本地文件的话,可以将代码中的第20行修改为:
const char output_link[] = "cctv.flv";
.
- 本程序只做了视频部分,因为视频部分比较占用CPU资源,音频部分占用小,而且视频部分代码比较简单好理解,后期增加音频流也基本一致,但是需要注意的有可能frame和packet并不是一一对应的,发送出去以后一定要用while来收回,还有就是注意时间轴的同步。
- 本程序使用的是纯C来进行编写的,主要是程序比较小,而且ffmpeg也是使用纯C写的,可以看到文件也都写在了一起,如果做的功能比较多,还是建议使用C++使用面向对象来完成,引入头文件时记的添加
extern "C"
,而且要用到多线程,包括视频解码线程,音频解码线程以及编码线程等等。
- 要使用音视频的缓存
AVFifoBuffer
。
- 增加QT界面,同时最好使用QT的项目管理,这样不用每次都输入那么多的参数了。如果不适用QT,不做界面,那么建议使用Makefile来管理项目。