https://blog.csdn.net/Hanghang_/article/details/105302384
,歡迎關注,點贊,評論。前言
本篇部落格是音視訊系列的續集與改進,有興趣的可以看我之前的部落格專欄。
音視訊系列部落格:HectoorZ:音視訊系列1:ffmpeg+rtmp拉流zhuanlan.zhihu.com
HectoorZ:音視訊系列2:ffmpeg将H.264解碼為RGBzhuanlan.zhihu.com
HectoorZ:音視訊系列3:使用ffmpeg + nginx搭建本地轉發伺服器zhuanlan.zhihu.com
HectoorZ:音視訊系列4:新手如何入門ffmpeg(以FLV解碼H.264為例)zhuanlan.zhihu.com
HectoorZ:音視訊系列5:ffmpeg拉流并引入ROS庫zhuanlan.zhihu.com
有興趣的小夥伴們可以看看。本節主要是對以下兩篇部落格進行改造更新,使得目前可以同時拉多個流,并且修改了一些之前的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:%dtpts:%ldn",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 contextn");
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 framen");
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 NULLn");
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