音视频系列6:ffmpeg多线程拉流

音视频系列6:ffmpeg多线程拉流

前言

本篇博客是音视频系列的续集与改进,有兴趣的可以看我之前的博客。
音视频系列博客
本节主要是对以下两篇博客进行改造升级,使得当前可以同时拉多个流,并且修改了一些之前的bug比如说段错误Segmentation fault。
音视频系列1:ffmpeg+rtmp拉流
音视频系列2:ffmpeg将H.264解码为RGB

用一张图表示接下来我要做的事情(橙色框框):
在这里插入图片描述
环境是Ubuntu18.04,ffmpeg4.1.5
主要有这么几个文件:
main.cpp
transdata.cpp
transdata.h

源码

主程序main.cpp分为两个部分,一是main()函数里申请pthread线程ID,开启线程,释放线程;二是athread线程函数的编写,每一个用户拉的流根据用户ID而定,比如用户ID为1的,拉流地址后面加1。
main.cpp:

#include <iostream>
#include "transdata.h"
using namespace std;
vector<Transdata> user_tran;
void *athread(void *ptr)
{
    int count = 0;
    int num = *(int *)ptr;
    //初始化
    while((user_tran[num].Transdata_init(num))<0)
    {
        cout << "init error "<< endl;
    }
    cout <<"My UserId is :"<< num << endl;
    //do something you want
    user_tran[num].Transdata_Recdata();
    user_tran[num].Transdata_free();
    return 0;
}
int main(int argc, char** argv)
{
    int ret;
    //申请内存  相当于注册
    for(int i = 0; i < 5 ; i++)
    {
        Transdata *p = new Transdata();
        user_tran.push_back(*p);
        user_tran[i].User_ID = i;
        cout << &user_tran[i] << endl;
        delete p;
    }
    //开启五个线程
    for(int i = 0; i < 5; i ++)
    {
        int *num_tran;
        num_tran = &user_tran[i].User_ID;
        ret = pthread_create(&user_tran[i].thread_id,NULL,athread,(void *)num_tran);
        if(ret < 0) return -1;
    }
    for(int i = 0; i < 5; i++)
    {
        pthread_join(user_tran[i].thread_id, NULL);/*等待进程t_a结束*/
    }
    return 0;
}

transdata.cpp transdata.h是拉流的功能函数。

transdata.cpp

#include "transdata.h"

Transdata::Transdata(){}
Transdata::~Transdata(){}


int Transdata::Transdata_free()
{
    av_bsf_free(&bsf_ctx);
    avformat_close_input(&ifmt_ctx);
    av_frame_free(&pframe);
    if (ret < 0 && ret != AVERROR_EOF)
    {
        printf( "Error occurred.\n");
        return -1;
    }
    return 0;
}


int Transdata::Transdata_Recdata()
{
	//可以自己增加LOG函数
    //LOGD("Transdata_Recdata entry %d",User_ID);
    int count = 0;
    while(av_read_frame(ifmt_ctx, &pkt)>=0)
    {
        //LOGD("av_read_frame test %d",User_ID);
        if (pkt.stream_index == videoindex) {
            // H.264 Filter
            if (av_bsf_send_packet(bsf_ctx, &pkt) < 0){
                cout << " bsg_send_packet is error! " << endl;
                av_packet_unref(&pkt);
                continue;
                //return -1;
            }
           // LOGD("av_bsf_send_packet test %d",User_ID);
            if (av_bsf_receive_packet(bsf_ctx, &pkt) < 0) {
                cout << " bsg_receive_packet is error! " << endl;
                av_packet_unref(&pkt);
                continue;
                //return -1;
            }
           // LOGD("av_bsf_receive_packet test %d",User_ID);
            count ++;
            if(count == 10) {
                printf("My id is %d,Write Video Packet. size:%d\tpts:%ld\n",User_ID, pkt.size, pkt.pts);
                count =0;
            }

            // Decode AVPacket
           // LOGD("Decode AVPacket ,ID is  %d",User_ID);
            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;
                    av_packet_unref(&pkt);

                    continue;
                    //return -1;
                }
            //    LOGD("avcodec_send_packet test %d",User_ID);
                //Get AVframe
                ret = avcodec_receive_frame(pCodecCtx, pframe);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    std::cout << "avcodec_receive_frame: " << ret << std::endl;
                    av_packet_unref(&pkt);
                    av_frame_unref(pframe);
                    continue;
                    // return -1;
                }
                //转成rgb
                avframeToCvmat(pframe);
            }
        }
        //Free AvPacket
        av_packet_unref(&pkt);
     //   av_free(pframe->data[0]);
        av_frame_unref(pframe); //后来才增加的 !! 每次重用之前应调用将frame复位到原始干净可用状态
        //https://www.cnblogs.com/leisure_chn/p/10404502.html

    }
        return 0;

}
//AVFrame 转 cv::mat
void Transdata::avframeToCvmat(const AVFrame * frame)
{
   // LOGD("avframeToCvmat  imshow1 , ID is  %d",User_ID);
    int width = frame->width;
    int height = frame->height;
    cv::Mat image(height, width, CV_8UC3);
  //  LOGD("avframeToCvmat  imshow2 , ID is  %d",User_ID);
    int cvLinesizes[1];
    cvLinesizes[0] = image.step1();
    SwsContext* conversion = sws_getContext(width, height, (AVPixelFormat) frame->format, width, height, AVPixelFormat::AV_PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    sws_scale(conversion, frame->data, frame->linesize, 0, height, &image.data, cvLinesizes);
  //  LOGD("avframeToCvmat  imshow3 , ID is  %d",User_ID);
    sws_freeContext(conversion);
  //  LOGD("avframeToCvmat  imshow4 , ID is  %d",User_ID);
    imshow(Simg_index,image);
    startWindowThread();//开启显示线程,专门用于显示
    waitKey(1);
    image.release();
  //  LOGD("avframeToCvmat  imshow5 , ID is  %d",User_ID);
}

int Transdata::Transdata_init(int num) {

    User_ID = num;  //用户ID
    Simg_index = to_string(num);
    cout << "Simg_index is : "<< Simg_index << endl;
    string str3 = to_string(num);
    cout << str3.size()<< endl;
    std::string video_name=str2+str3;
    const char *video_filename = video_name.c_str();//string转const char*

    cout << video_filename << endl;

    //新增
    ifmt_ctx = avformat_alloc_context();
    //pkt = (AVPacket *)av_malloc(sizeof(AVPacket));

    //Register
    av_register_all();
    //Network
    avformat_network_init();
    //Input
    if ((ret = avformat_open_input(&ifmt_ctx, video_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");
        return -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");
        return -1;
    }
    //find filter
    buffersrc = av_bsf_get_by_name("h264_mp4toannexb");
    //
    if(av_bsf_alloc(buffersrc, &bsf_ctx) < 0) {
        printf("av_bsf_alloc is error");
        return -1;
    }
    if(codecpar != NULL) {
        if (avcodec_parameters_copy(bsf_ctx->par_in, codecpar) < 0) {
            printf("avcodec_parameters_copy is error");
            return -1;
        }
        if (av_bsf_init(bsf_ctx) < 0) {
            printf("av_bsf_init is error");
            return -1;
        }
    }
    else {
        printf("codecpar is NULL\n");
        return -1;
    }
        return 0;
}

transdata.h

#ifndef VERSION1_0_TRANSDATA_H
#define VERSION1_0_TRANSDATA_H

#include <iostream>
extern "C"
{
#include "libavformat/avformat.h"
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#include <libavutil/samplefmt.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include "libavutil/avconfig.h"
#include <libavutil/imgutils.h>
#include "libswscale/swscale.h"
};
#include "opencv2/core.hpp"
#include<opencv2/opencv.hpp>
//#include "LogUtils.h"
using namespace std;
using namespace cv;

class Transdata
{
public:
    Transdata();
    ~Transdata();
    AVFormatContext *ifmt_ctx = NULL;
    AVPacket pkt;
    AVFrame *pframe = NULL;
    int ret, i;
    int videoindex=-1;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    const AVBitStreamFilter *buffersrc  =  NULL;
    AVBSFContext *bsf_ctx;
    AVCodecParameters *codecpar = NULL;
    std::string str2= "rtmp://localhost:1935/rtmplive/test";
    //std::string str2= "rtmp://47.100.110.164:1935/live/test";
    //const char *in_filename  = "rtmp://localhost:1935/rtmplive";   //rtmp地址
    //const char *in_filename  = "rtmp://58.200.131.2:1935/livetv/hunantv";   //芒果台rtmp地址
    cv::Mat image_test;
    int Transdata_init(int num);
    int Transdata_Recdata();
    int Transdata_free();
    void avframeToCvmat(const AVFrame * frame);
    int User_ID;
    string Simg_index;
    pthread_t thread_id;
};


#endif //VERSION1_0_TRANSDATA_H
#ifndef VERSION1_0_TRANSDATA_H
#define VERSION1_0_TRANSDATA_H

#include <iostream>
extern "C"
{
#include "libavformat/avformat.h"
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#include <libavutil/samplefmt.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include "libavutil/avconfig.h"
#include <libavutil/imgutils.h>
#include "libswscale/swscale.h"
};
#include "opencv2/core.hpp"
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

class Transdata
{
public:
    Transdata();
    ~Transdata();
    AVFormatContext *ifmt_ctx = NULL;
    AVPacket pkt;
    AVFrame *pframe = NULL;
    int ret, i;
    int videoindex=-1;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    const AVBitStreamFilter *buffersrc  =  NULL;
    AVBSFContext *bsf_ctx;
    AVCodecParameters *codecpar = NULL;
    std::string str2= "rtmp://localhost:1935/rtmplive/test";
    int Transdata_init(int num);
    int Transdata_Recdata();
    int Transdata_free();
    void avframeToCvmat(const AVFrame * frame);
    int User_ID;
    string Simg_index;
    pthread_t thread_id;
};

#endif //VERSION1_0_TRANSDATA_H

遇到的问题

①由于要把增加用户ID增加进拉流地址,拉流地址的格式是const char * ,由于ID是整型,这可以把int类型的ID转成string类型,拼接到服务器的string类型IP去,然后再转成const char *,这样比直接在const char *拼接简单,至少我还不知道有什么其他方法。

例子:

int num = 5;
string str2 = {"test"};
string str3 = to_string(num);
std::string video_name=str2+str3;
const char *video_filename = video_name.c_str();//string转const char*

输出为test5

②imshow处显示久了会冻结,不更新
这个问题谷歌后,发现有很多这样的问题,opencv里给的官方拉流demo也确实没有考虑这样的问题,出现这个问题是因为如果接收速度太快而显示速度太慢就会矛盾,从而产生问题。实际上处理图片和接收图片最后是放在两个线程里,接收图片一个线程,显示图片一个线程,接收图片后把图片放进一个队列里,显示图片线程就去取,如果接收得太快,显示图片线程发现有队列里有两张图片,那么就丢掉之前的一张,只拿后面一张,这样就不会发生冲突了。
那么opencv里也有这么一个函数,应该是能够实现上面所说,startWindowThread(),官方给的api说明也不够,但是我用上去之后发现确实没问题了。

原来是这样会出问题:

while(1)
{
    RecImage();
    imshow(“test0”,img);
    waitKey(1);
}

修改后:

while(1)
{
    RecImage();
    imshow(“test0”,img);
    startWindowThread();//开启一个线程专门显示图片
    waitKey(1);
}

③各种段错误Segmentation Fault问题,千万要记得申请内存,释放内存,出现Segmentation Fault也不要慌张,把你所定义的变量从头到尾检查一遍,基本就能够解决问题了,另外可以使用gdb调试、查看程序开启前和开启后的内存情况,或是增加LOG库,保存日志,从而发现问题。

注意事项

CMakelists.txt可以参照我之前的博客,这里就不放了。音视频系列2:ffmpeg将H.264解码为RGB
拉远程流需要修改transdata.h里的str字符串。
需要多少个流可以在for里面修改个数,注意申请、开启、释放的值都要修改。
可以使用本地推流测试,可参考音视频系列3:使用ffmpeg + nginx搭建本地转发服务器
由于电脑太旧,CPU不行,我这里同时拉取两个流是稳定运行的,但拉取五个流以上就会很卡,但还是能够运行的,所以说,想要拉取多个流,首先要保证电脑性能ok。

做到这一步,还远远不够,下一步是增加socket,与中转服务器进行通信,拉流之前中转服务器会告诉我谁需要注册,谁需要推流,然后我就做相应的操作啦。

参考文献:
https://zhuanlan.zhihu.com/p/80350418
http://www.man7.org/linux/man-pages/man3/pthread_create.3.html pthread 文档
https://zhuanlan.zhihu.com/p/38136322

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++是一种通用的编程语言,广泛应用于软件开发领域。它是C语言的扩展,提供了更多的特性和功能。C++具有高效性、可移植性和灵活性等特点,被广泛用于开发各种类型的应用程序,包括桌面应用、嵌入式系统、游戏开发等。 FFmpeg是一个开源的跨平台音视频处理工具库,它提供了一组丰富的音视频处理功能和工具,包括音视频编解码、格式转换、流媒体处理等。通过使用FFmpeg,我们可以实现音视频的采集、处理和播放等功能。 如果你想使用FFmpeg来进行拉流并将其存储到本地,你可以按照以下步骤进行操作: 1. 安装FFmpeg:首先需要在你的系统上安装FFmpeg库。你可以从FFmpeg官方网站下载最新版本的源代码,并按照官方提供的编译指南进行编译和安装。 2. 编写C++代码:使用C++编写一个程序来实现拉流并存储到本地的功能。你可以使用FFmpeg提供的API来进行音视频的解码和存储操作。具体的代码实现会涉及到FFmpeg的相关函数调用和参数设置,需要参考FFmpeg的官方文档或者其他相关资源。 3. 配置输入和输出:在代码中配置输入流和输出文件的相关参数。你需要指定要拉取的流的URL或者文件路径,以及存储到本地的文件名和格式等信息。 4. 执行拉流存储:运行你编写的C++程序,它将会使用FFmpeg库来进行拉流并将其存储到本地。你可以根据需要设置一些额外的参数,如视频的分辨率、音频的采样率等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值