前言
音视频系列博客:
音视频系列1:ffmpeg+rtmp拉流
音视频系列2:ffmpeg将H.264解码为RGB
音视频系列3:使用ffmpeg + nginx搭建本地转发服务器
音视频系列4:新手如何入门ffmpeg(以FLV解码H.264为例)
有兴趣的小伙伴们可以看看。
上节我们更新了ffmpeg拉流中过时的API,本节将对上节的代码进行封装,并引入ROS包,使得ffmpeg拉来的流可以通过ROS系统进行传输,伸手党可以直接拉到最下面,附github代码。
封装
原本只有一个main.cpp,现在我们定义一个Transdata类。
所以现在扩展成三个文件:
transdata.cpp
transdata.h
Trans_node.cpp
其中,transdata.cpp里写明类的函数的具体过程,transdata.h用于定义函数名,Trans_node.cpp用于实例化Transdata类,并调用其函数。
Trans_node.cpp
:
#include <iostream>
#include "transdata.h"
Transdata transdata;
using namespace std;
int main(int argc, char** argv)
{
if(transdata.Transdata_init() < 0)
{
cout <<"init error !" << endl;
return -1;
}
while(1)
{
transdata.Transdata_Recdata();
}
transdata.Transdata_free();
return 0;
}
transdata.cpp
里定义了几个重要的函数,分别是:
初始化:Transdata_init()
int Transdata::Transdata_init() {
//Register
av_register_all();
//Network
avformat_network_init();
//Input
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf("Could not open input file.");
return -1;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf("Failed to retrieve input stream information");
return -1;
}
videoindex = -1;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
codecpar = ifmt_ctx->streams[i]->codecpar;
}
}
//Find H.264 Decoder
pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (pCodec == NULL) {
printf("Couldn't find Codec.\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (!pCodecCtx) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Couldn't open codec.\n");
return -1;
}
pframe = av_frame_alloc();
if (!pframe) {
printf("Could not allocate video frame\n");
exit(1);
}
buffersrc = av_bsf_get_by_name("h264_mp4toannexb");
if(av_bsf_alloc(buffersrc, &bsf_ctx) < 0)
return -1;
if(avcodec_parameters_copy(bsf_ctx->par_in,codecpar) < 0)
return -1;
if(av_bsf_init(bsf_ctx) < 0)
return -1;
}
拉流:Transdata_Recdata()
int Transdata::Transdata_Recdata()
{
if(av_read_frame(ifmt_ctx, &pkt)<0) {
return -1;
}
if (pkt.stream_index == videoindex) {
// H.264 Filter
if(av_bsf_send_packet(bsf_ctx, &pkt) < 0)
{
cout << " bsg_send_packet is error! " << endl;
return -1;
}
if(av_bsf_receive_packet(bsf_ctx, &pkt) < 0)
{
cout << " bsg_receive_packet is error! " << endl;
return -1;
}
printf("Write Video Packet. size:%d\tpts:%ld\n", pkt.size, pkt.pts);
// Decode AVPacket
if(pkt.size)
{
ret = avcodec_send_packet(pCodecCtx, &pkt);
if (ret < 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
std::cout << "avcodec_send_packet: " << ret << std::endl;
return -1;
}
//Get AVframe
ret = avcodec_receive_frame(pCodecCtx, pframe);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
std::cout << "avcodec_receive_frame: " << ret << std::endl;
return -1;
}
//AVframe to rgb
AVFrame2Img(pframe,image_test);
}
}
//Free AvPacket
av_packet_unref(&pkt);
return 0;
}
释放资源:Transdata_free()
int Transdata::Transdata_free()
{
av_bsf_free(&bsf_ctx);
avformat_close_input(&ifmt_ctx);
if (ret < 0 && ret != AVERROR_EOF)
{
printf( "Error occurred.\n");
return -1;
}
return 0;
}
测试无问题,继续下一步,引入ROS
引入ROS库
要引入ROS库,可又不懂,那么推荐你去看ROS WIKI,上面介绍得很详细,并且有中文翻译,如果还觉得吃力,那么可以去看看古月居的ROS二十一讲。
,我也是刚接触到ROS,于是我先实现了一个小demo,就是定义两个node,一发一收传送字符串,建议对ROS不熟悉的小伙子们可以先按照我的步骤去实现一遍,可以帮助熟悉ROS的编程。
相关的教程在此:
编写简单的消息发布器和订阅器 (C++)
测试消息发布器和订阅器
写完测试能用之后,进而把刚刚我们封装好的代码拉进来,这里具体的步骤较多,就不详细说了,等移进来调用没问题之后,就可以考虑考虑如何发送图像数据了。
还是直接上官网的教程:
Writing a Simple Image Publisher (C++)
Writing a Simple Image Subscriber (C++)
需要注意的是,如果直接引入cv_bridge包或者是image_transport包,是会报错的,你还要在CMakeLists.txt和package.xml进行修改。
修改CMakeLists.txt
:增加库名
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
cv_bridge
image_transport
)
修改package.xml
: 增加以下内容
<build_depend>image_transport</build_depend>
<exec_depend>image_transport</exec_depend>
<build_depend>cv_bridge</build_depend>
<exec_depend>cv_bridge</exec_depend>
这样才可以调用。
另外,因为你要对图像数据进行操作(传送到message里),所以你还得使用互斥锁,保证数据的安全。
好了,话不多说,直接上源码。
https://github.com/Hectoor/Ffmpeg_ros_image_trans
测试图: 一发一收
如果我的文章对你有帮助,欢迎点赞、评论、关注。