Qt6 ffmpeg 音频和视频(非同步)推流到nginx-rtmp

2023-11-02

main.cpp


#include <QApplication>
#include "Controller.h"

using namespace std;
int main(int argc,char *argv[])
{
    QApplication a(argc,argv);

    qDebug() << "main thread:" << QThread::currentThreadId();

    Controller* controller = new Controller();
    emit controller->operate("copy");
    a.exec();
}

Controller.h


#include <QThread>

class Controller : public QObject
{
    Q_OBJECT
public:
    Controller(QObject* parent = nullptr);
    ~Controller();

public slots:
    void handleResults(const QString &des);

signals:
    void operate(const QString &cmd);

private:
    QThread thread;
};

Controller.cpp


#include "Controller.h"
#include "AudioRecordWorker.h"
#include "VideoRecordWorker.h"
#include <QDebug>

Controller::Controller(QObject* parent)
    : QObject(parent)
{   
#if 1
    //音频推流
    AudioRecordWorker *audioRecord = new AudioRecordWorker();
    audioRecord->moveToThread(&thread);
    connect(&thread, &QThread::finished, audioRecord, &QObject::deleteLater);
    connect(this, &Controller::operate, audioRecord, &AudioRecordWorker::doSomething);
    connect(audioRecord, &AudioRecordWorker::resultNotify, this, &Controller::handleResults);
#else
    //视频推流
    VideoRecordWorker *videoRecord = new VideoRecordWorker();
    videoRecord->moveToThread(&thread);
    connect(videoRecord, &VideoRecordWorker::resultNotify, this, &Controller::handleResults);
    connect(&thread, &QThread::finished, videoRecord, &QObject::deleteLater);
    connect(this, &Controller::operate, videoRecord, &VideoRecordWorker::doSomething);
#endif

    thread.start();
}

Controller::~Controller()
{
    thread.quit();
    thread.wait();
}

void Controller::handleResults(const QString &des)
{
    qDebug() << "handleResults()" << des << "thread:" << QThread::currentThreadId();
}

AudioRecordWorker.h


#pragma once
#include <QObject>
#include <string>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

class XMediaEncode;

class VideoRecordWorker : public QObject
{
    Q_OBJECT
public:
    explicit VideoRecordWorker(QObject *parent = nullptr);
    ~VideoRecordWorker();
    void mediaEncodeInit();
    void startRecord();
    void recordParameter(int sampleRate,int channels, int sampleByte , int nbSample);
public slots:
    void doSomething(const QString& cmd);

signals:
    void resultNotify(const QString& des);
private:
    std::string inUrl = "rtsp://test:test@192.168.1.4";
    //nginx-rtmp 直播服务器rtmp推流URL
    std::string outUrl = "rtmp://0.0.0.0/live";

    XMediaEncode *mediaEncode;
    cv::VideoCapture cam;
    cv::Mat frame;
    int sampleRate = 44100;
    int channels = 2;
    int sampleByte = 2;
    int nbSample = 1024;
    int inWidth = 0;
    int inHeight = 0;
    int fps = 0;
};

VideoRecordWorker.cpp


#include <stdexcept>        // 1. 更换包含头文件
#include <exception>
#include <iostream>
#include <string>
#include <QDebug>
#include <QThread>
#include "VideoRecordWorker.h"
#include "XMediaEncode.h"

using namespace cv;
using namespace std;

VideoRecordWorker::VideoRecordWorker(QObject *parent)
    : QObject(parent)
{

}


VideoRecordWorker::~VideoRecordWorker()
{
    if(cam.isOpened())
        cam.release();
    qDebug() << "~VideoRecordWorker()" << "thread:" << QThread::currentThreadId();
}

void VideoRecordWorker::mediaEncodeInit()
{

}

void VideoRecordWorker::doSomething(const QString &cmd)
{
    qDebug() << "doSomething()" << cmd << "thread:" << QThread::currentThreadId();
    startRecord();
    emit resultNotify("doSomething ok!");
}


void VideoRecordWorker::startRecord()
{       
    //编码器和像素格式转换
    mediaEncode =  XMediaEncode::Get(0);
    //cam.open(inUrl);
    //1.使用opencv打开rtsp相机
    cam.open(0);
    if(!cam.isOpened())
    {
        throw logic_error("cam open failed");
    }
    cout<<inUrl<<"cam open success"<<endl;
    inWidth = cam.get(CAP_PROP_FRAME_WIDTH);
    inHeight = cam.get(CAP_PROP_FRAME_HEIGHT);
    fps = cam.get(CAP_PROP_FPS);

    //注册所有的编解码器
    //注册所有的封装器
    //注册所有网络协议
    mediaEncode->register_ffmpeg();

    //2.初始化像素格式转换的上下文初始化
    //3.输出的数据结构
    mediaEncode->inWidth = inWidth;
    mediaEncode->inHeight = inHeight;
    mediaEncode->outWidth = inWidth;
    mediaEncode->outHeight = inHeight;
    mediaEncode->InitScale();
    if(!mediaEncode->InitVideoCodec())
    {   
        std::cout<<"InitVideoCodec failed!"<<std::flush;
        if(cam.isOpened())
        cam.release();
        return;
    }
    
    //初始化封装器的上下文
    mediaEncode->Init(outUrl.c_str());
    //添加视频或者音频
    mediaEncode->AddStream(mediaEncode->vc);        
    mediaEncode->SendHead();

    for(;;)
    {
        //读取rtsp视频帧,解码视频帧
        if(!cam.grab())
        {
            continue;
        }
        //yuv转为rgb          
        //输入的数据结构
        if(!cam.retrieve(frame))
        {
            continue;
        }
        
        //rgb to yuv
        mediaEncode->inPixSize = frame.elemSize();
        AVFrame *yuv = mediaEncode->RGBToYUV((char*)frame.data);
        if(!yuv) continue;

        AVPacket *pkt = mediaEncode->EncodeVideo(yuv);
        if(!pkt) continue;
        mediaEncode->SendFrame(pkt);
    }       
}

void VideoRecordWorker::recordParameter(int sampleRate,int channels, int sampleByte , int nbSample)
{
    sampleRate = sampleRate;
    channels = channels;
    sampleByte = sampleByte;
    nbSample = nbSample;
}

AudioRecordWorker.h


#include <QObject>
#include <QAudioSource>
#include <QAudioFormat>
#include <QMediaDevices>
#include <QAudioInput>
#include <string>

class XMediaEncode;

class AudioRecordWorker : public QObject
{
    Q_OBJECT
public:
    explicit AudioRecordWorker(QObject *parent = nullptr);
    ~AudioRecordWorker();
    void mediaEncodeInit();
    void startRecord();
    void recordParameter(int sampleRate,int channels, int sampleByte , int nbSample);
public slots:
    void doSomething(const QString& cmd);

signals:
    void resultNotify(const QString& des);
private:
    QAudioFormat format;
    QAudioDevice info;
    QAudioSource *audio = nullptr;
    QIODevice *io = nullptr;
    XMediaEncode *mediaEncode = nullptr; 
    std::string outUrl = "rtmp://0.0.0.0/live";

    int sampleRate = 44100;
    int channels = 2;
    int sampleByte = 2;
    int nbSample = 1024;
};

AudioRecordWorker.cpp


#include <QDebug>
#include <QThread>
#include <iostream>
#include "AudioRecordWorker.h"
#include "VideoRecordWorker.h"
#include "XMediaEncode.h"
using namespace std;

AudioRecordWorker::AudioRecordWorker(QObject *parent)
    : QObject(parent)
{
     //1.qt音频开始录制
    format.setSampleRate(sampleRate);
    format.setChannelCount(channels);
    format.setSampleFormat(QAudioFormat::Int16);
    
    info = QMediaDevices::defaultAudioInput();
    if (!info.isFormatSupported(format))
    {
        qWarning() << "Default format not supported, trying to use the nearest.";
    }
    audio = new QAudioSource(format,this);
    qDebug() << "AudioRecordWorker()" << "thread:" << QThread::currentThreadId();

    mediaEncodeInit();
}

void AudioRecordWorker::mediaEncodeInit()
{
    //初始化XMediaEncode
    mediaEncode =  XMediaEncode::Get();
    mediaEncode->register_ffmpeg();
    mediaEncode->channels = channels;
    mediaEncode->nbSample = 1024;
    mediaEncode->sampleRate = sampleRate;
    mediaEncode->inSampleFmt = XSampleFMT::X_S16;
    mediaEncode->outSampleFmt = XSampleFMT::X_FLATP;

    //开始录制音频
    io = audio->start();

    if(!mediaEncode->InitResample())
    {
        std::cout<<"InitResample failed"<<flush;
        return ;
    }
    
    if(!mediaEncode->InitAudioCode())
    {
        return;
    }

    //a.创建输出封装器上下文 
    if(!mediaEncode->Init(outUrl.c_str()))
    {
        std::cout<<"avformat_alloc_output_context2"<<flush;
    }

    //b.添加音频流
    if(!mediaEncode->AddStream(mediaEncode->ac))
    {
        std::cout<<"AddStream failed"<<flush;
        return;
    }
   
    //打开rtmp的网络输出IO
    //写入封装头
    if(!mediaEncode->SendHead())
    {
        std::cout<<"SendHead failed"<<std::endl;
    }
}

AudioRecordWorker::~AudioRecordWorker()
{
    qDebug() << "~AudioRecordWorker()" << "thread:" << QThread::currentThreadId();
}

void AudioRecordWorker::doSomething(const QString &cmd)
{
    qDebug() << "doSomething()" << cmd << "thread:" << QThread::currentThreadId();
    startRecord();
    emit resultNotify("doSomething ok!");
}

void AudioRecordWorker::startRecord()
{

    int frameSize = mediaEncode->nbSample*channels*sampleByte;
    char *buf = new char[frameSize];
    qDebug()<<"size = "<<frameSize;
    int size = 0;
    for(;;)
    {
        qint64 rev_len = io->bytesAvailable();
        std::cout<<rev_len<<std::endl;
        //一次读取一帧音频
        if(rev_len < frameSize)
        {
            QThread::msleep(1);
            continue;
        }

        int size = 0;
        while(size != frameSize)
        {
            int len = io->read(buf+size,frameSize - size);
            size += len;
        }

        if(size != frameSize) continue;
        //已经读一帧源数据
        cout << size << " "<<flush;
        //重采样源数据
        AVFrame *pcm = mediaEncode->Resample(buf);
        if(!pcm)
        {
            std::cout<<"pcm == NULL"<<flush;
        }
        //pts 运算
        //nb_sample/sample_rate = 一帧音频的秒数 
        //timebase pts = sec*timebase.den    
        AVPacket *pkt =  mediaEncode->EncodeAudio(pcm);
        if(!pkt) continue;
        //cout<<pkt->size<<" "<<flush;

        //推流
        mediaEncode->SendFrame(pkt);
    }
    delete buf;
}


void AudioRecordWorker::recordParameter(int sampleRate,int channels, int sampleByte , int nbSample)
{
    sampleRate = sampleRate;
    channels = channels;
    sampleByte = sampleByte;
    nbSample = nbSample;
}

XMediaEncode.h


#pragma once

#include <string>
class AVFrame;
class AVPacket;
class AVCodecContext;
class AVFormatContext;
class AVStream;


enum XSampleFMT
{
    X_S16 = 1,
    X_FLATP = 8
};

//音视频编码接口类
class XMediaEncode
{
public:
    //输入参数
    int inWidth = 1280;
    int inHeight = 720;
    int inPixSize = 3;
    int channels = 2;
    int sampleRate = 44100;
    XSampleFMT inSampleFmt = X_S16;

    //输出参数
    int outWidth = 1280;
    int outHeight = 720;
    int bitrate = 4000000;//压缩后每秒视频的bit位大小50KB
    int fps = 25;
    int nbSample = 1024;
    XSampleFMT outSampleFmt = X_FLATP;

    //工厂生产方法
    static XMediaEncode *Get(unsigned char index = 0);

    virtual void register_ffmpeg() = 0;

    //初始化封装器的上下文
    virtual bool Init(const char *url) = 0;

    //初始化像素格式转换的上下文初始化
    virtual bool InitScale() = 0;

    //音频重采样上下文初始化
    virtual bool InitResample() = 0;

    //音频编码器初始化
    virtual bool InitAudioCode() = 0;

    virtual AVFrame* Resample(char *data) = 0;

    virtual AVFrame* RGBToYUV(char *rgb) = 0;

    //编码器的初始化
    virtual bool InitVideoCodec() = 0;

    //视频编码
    virtual AVPacket *EncodeVideo(AVFrame *frame) = 0;

    //音频编码
    virtual AVPacket *EncodeAudio(AVFrame *frame) = 0;

    //添加视频或者音频
    virtual bool AddStream(AVCodecContext *c) = 0;

    //打开rtmp网络IO,发送封装头
    virtual bool SendHead() = 0;

    //rtmp 帧推流
    virtual bool SendFrame(AVPacket *pkt) = 0;

    virtual ~XMediaEncode();

    //视频编码器的上下文
    AVCodecContext *vc = 0;

    //音频编码器的上下文
    AVCodecContext *ac = 0; //音频编码器上下文
    AVFormatContext *ic = 0;
protected:
    XMediaEncode();
    //rtmp flv 封装器
    std::string outUrl = "";
    AVStream *vs = 0;
    AVStream *as = 0;
};

XMediaEncode.cpp


#include "XMediaEncode.h"
#include <iostream>
#include <QDebug>
using namespace std;

extern "C"
{
    #include <libswscale/swscale.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswresample/swresample.h>
}

class CXMediaEncode:public XMediaEncode
{
public:
    void close()
    {
        if(vsc)
        {
            sws_freeContext(vsc);
            vsc = NULL;
        }
        if(asc)
        {
            swr_free(&asc);
        }
        if(yuv)
        {
            av_frame_free(&yuv);
        }

        if(vc)
        {
            avcodec_free_context(&vc);
        }

        if(pcm)
        {
            av_frame_free(&pcm);
        }

        if(ic)
        {
            avformat_close_input(&ic);
            vs = NULL;
        }
        vc = NULL;
        outUrl = "";
   
        vpts = 0;
        av_packet_unref(&vpacket);
        apts = 0;
        av_packet_unref(&apacket);
    }

    void register_ffmpeg()
    {
        //注册所有的编解码器
        avcodec_register_all();

        //注册所有的封装器
        av_register_all();

        //注册所有网络协议
        avformat_network_init();
    }

    bool Init(const char *url)
    {
        ///5 封装器和视频流配置
        //a.创建输出封装器上下文 
        int ret = avformat_alloc_output_context2(&ic,0,"flv",url);
        this->outUrl = url;
        if(ret != 0)
        {
            char buf[1024] = {0};
            av_strerror(ret,buf,sizeof(buf) - 1);
            cout<<buf;
            return false;
        }
        return true;
    }


    bool InitAudioCode()
    {
        if(!CreateAudioCodec(AV_CODEC_ID_AAC))
        {
            return false;
        }
        ac->bit_rate = 40000;
        ac->sample_rate = sampleRate;
        ac->sample_fmt = AV_SAMPLE_FMT_FLTP;
        ac->channels = channels;
        ac->channel_layout = av_get_default_channel_layout(channels);

        return OpenCodec(ac);
    }

    bool InitVideoCodec()
    {

        //4初始化编码上下文
        //a 找到编码器
        codec = avcodec_find_encoder(AV_CODEC_ID_H264);
        if(!CreateVideoCodec(AV_CODEC_ID_H264))
        {
            cout<<"Can't find h264 encoder!"<<endl;
            return false;
        }


        vc->bit_rate = 50*1024*8; //压缩后每秒视频的bit位大小 50kb
        vc->width = outWidth;
        vc->height = outHeight;
        vc->time_base = {1,fps}; //时间基数
        vc->framerate = {fps,1};

        //画面组的大小,多少帧一个关键帧
        vc->gop_size = 50;
        vc->max_b_frames = 0;
        vc->pix_fmt = AV_PIX_FMT_YUV420P;

        //d 打开编码器
        return OpenCodec(vc);
    }

    AVPacket *EncodeAudio(AVFrame *frame)
    {
        //pts 运算
        //nb_sample/sample_rate = 一帧音频的秒数 
        //timebase pts = sec*timebase.den
        pcm->pts = apts;
        apts += av_rescale_q(pcm->nb_samples,{1,sampleRate},ac->time_base); 
        int ret = avcodec_send_frame(ac,pcm);
        if(ret != 0) return NULL;

        av_packet_unref(&apacket);
        ret = avcodec_receive_packet(ac,&apacket);
        if(ret != 0) return NULL;
    
        cout<<apacket.size<<" "<<flush;
        return &apacket;
    }

    AVPacket *EncodeVideo(AVFrame* frame)
    {
        av_packet_unref(&vpacket);

        //h264编码    
        frame->pts = vpts;
        vpts++;
        int ret = avcodec_send_frame(vc,frame);
        if(ret!=0)
            return NULL;
        //每次都会调用av_frame_unref(frame)
        ret = avcodec_receive_packet(vc,&vpacket);
        if(ret != 0 || vpacket.size <= 0)
            return NULL;
        return &vpacket;
    }

    bool InitScale()
    {
        //2.初始化格式转换的上下文
        vsc = sws_getCachedContext(vsc,
            inWidth,inHeight,AV_PIX_FMT_BGR24,//原宽度高度
            outWidth,outHeight,AV_PIX_FMT_YUV420P,//输出宽,高,像素格式
            SWS_BICUBIC,//尺寸变化算法
            0,0,0
            );
        if(!vsc)
        {
            cout<<"sws_getCachedContext failed!";
            return false;
        }
        //3.输出的数据结构
        yuv = av_frame_alloc();
        yuv->format = AV_PIX_FMT_YUV420P;
        yuv->width = inWidth;
        yuv->height = inHeight;
        yuv->pts = 0;
        //分配yuv空间
        int ret = av_frame_get_buffer(yuv,32);
        if(ret != 0)
        {
            char buf[1024] = {0};
            av_strerror(ret, buf,sizeof(buf) - 1);
            throw logic_error(buf);
        }
        return true;
    }

    AVFrame* RGBToYUV(char *rgb)
    {
        //rgb to yuv
        //输入的数据格式
        uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
        //bgrbgrbgr
        //plane inData[0]bbbb gggg rrrr
        indata[0] = (uint8_t*)rgb;
        int insize[AV_NUM_DATA_POINTERS] = {0};
        //一行(宽)数据的字节数
        insize[0] = inWidth * inPixSize;

        int h = sws_scale(vsc,indata,insize,0,inHeight, //输入数据
            yuv->data,yuv->linesize);
        if(h<=0)
        {
            return NULL;
        }
        return yuv;
    }

    bool InitResample()
    {
        //音频重采样 上下文初始化
        asc = swr_alloc_set_opts(asc,
            av_get_default_channel_layout(channels), (AVSampleFormat)outSampleFmt, sampleRate, //输出格式
            av_get_default_channel_layout(channels),(AVSampleFormat)inSampleFmt, sampleRate, //输入格式
            0,0);
        if(!asc)
        {
            cout<<"swr_alloc_set_opts failed!";
            return false;
        }
        int ret = swr_init(asc);
        if(ret != 0)
        {
            char err[1024] = {0};
            av_strerror(ret,err,sizeof(err) - 1);
            cout<<err<<endl;
            return false;
        }
        std::cout<<"音频重采样上下文初始化成功"<<endl;

        //音频从采样空间的分配
        pcm = av_frame_alloc();
        pcm->format = outSampleFmt;
        pcm->channels = channels;
        pcm->channel_layout = av_get_default_channel_layout(channels);
        pcm->nb_samples = nbSample; //一帧音频一个通道的采样数
        ret = av_frame_get_buffer(pcm,0); //给pcm分配存储空间
        if(ret != 0)
        {
            char err[1024] = {0};
            av_strerror(ret,err,sizeof(err) - 1);
            cout<<err<<endl;
            return false;
        }
        return true;
    }


    AVFrame* Resample(char *data)
    {
        const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
        indata[0] = (uint8_t*)data;
        int len = swr_convert(
        asc,pcm->data,pcm->nb_samples, //输出参数,输出存储地址和样本数量
        indata,pcm->nb_samples);

        qDebug()<<"swr_convert = "<<len<<" ";  
        if(len <= 0)
        {
            return NULL;
        }
        return pcm;
    }

    bool AddStream(AVCodecContext *c)
    {
        if(!c) return false;

        //b.添加视频流
        AVStream *st = avformat_new_stream(ic,NULL);
        if(!st)
        {
            cout<<"avformat_new_stream failed"<<endl;
            return false;
        }
        st->codecpar->codec_tag = 0;

        //从编码器复制参数
        avcodec_parameters_from_context(st->codecpar, c);
        av_dump_format(ic,0,outUrl.c_str(),1);
        if(c->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            vc = c;
            vs = st;
        }
        else if(c->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            ac = c;
            as = st;
        }
        return true;
    }

    bool SendHead()
    {
        //打开rtmp的网络输出IO
        int ret = avio_open(&ic->pb,outUrl.c_str(),AVIO_FLAG_WRITE);
        if(ret != 0)
        {
            char buf[1024] = {0};
            av_strerror(ret,buf,sizeof(buf) - 1);
            cout<<buf<<endl;
            return false;
        }

        //写入封装头
        ret = avformat_write_header(ic,NULL);
        if(ret != 0)
        {
            char buf[1024] = {0};
            av_strerror(ret,buf,sizeof(buf) - 1);
            cout<<buf<<endl;
            return false;
        }
        return true;
    }

    bool SendFrame(AVPacket *pkt)
    {
        if(!pkt || pkt->size <= 0 || !pkt->data) 
        {
            std::cout<<"pkt is NULL"<<flush;
            return false;
        }
        AVRational stime;
        AVRational dtime;
        //判断音视频
        if(vc && vs && pkt->stream_index == vs->index)
        {
            stime = vc->time_base;
            dtime = vs->time_base;
        }
        else if(ac && as && pkt->stream_index ==as->index)
        {
            stime = ac->time_base;
            dtime = as->time_base;

        }
        else
        {
            return false;
        }
        //推流
        pkt->pts = av_rescale_q(pkt->pts,stime,dtime);
        pkt->dts = av_rescale_q(pkt->dts,stime,dtime);
        pkt->duration = av_rescale_q(pkt->duration,stime,dtime);

        int ret = av_interleaved_write_frame(ic,pkt);
        if(ret == 0)
        {
            cout<<"#"<<flush;
        }
        return true;
    }

private:
    bool OpenCodec(AVCodecContext *c)
    {
        //打开音频编码器
        int ret = avcodec_open2(c,0,0);
        if(ret != 0)
        {
            char err[1024] = {0};
            av_strerror(ret,err,sizeof(err) - 1);
            cout<<err<<endl;
            avcodec_free_context(&c);
            cout<<"avcodec_open2 failed!"<<endl;
            return false;
        }
        cout<<"avcodec_open2 success!"<<endl;
        return true;
    }

    bool CreateAudioCodec(AVCodecID cid)
    {
        //一次读取一帧音频的字节数
        ///4 初始化音频编码器
        AVCodec *codec = avcodec_find_encoder(cid);
        if(!codec)
        {
            cout<<"avcodec_find_encoder  failed!"<<endl;
            return false;
        }
        ac = avcodec_alloc_context3(codec);
        if(!ac)
        {
            cout<<"avcodec_alloc_context3 cid failed!"<<endl;
            return false;
        }
        cout<<"avcodec_alloc_context3 success!"<<endl;
        ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        ac->thread_count = 8;
        return true;
    }

    bool CreateVideoCodec(AVCodecID cid)
    {
        //一次读取一帧音频的字节数
        ///4 初始化音频编码器
        AVCodec *codec = avcodec_find_encoder(cid);
        if(!codec)
        {
            cout<<"avcodec_find_encoder  failed!"<<endl;
            return false;
        }
        vc = avcodec_alloc_context3(codec);
        if(!vc)
        {
            cout<<"avcodec_alloc_context3 cid failed!"<<endl;
            return false;
        }
        cout<<"avcodec_alloc_context3 success!"<<endl;
        vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        vc->thread_count = 8;
        return true;
    }

    SwsContext *vsc = NULL; //像素格式上下文
    SwrContext *asc = NULL; //像素格式上下文
    AVFrame *yuv = NULL;
    AVFrame *pcm = NULL;    //输出的pcm
    AVPacket vpacket = {0}; //视频帧
    AVPacket apacket = {0}; //音频帧
    AVCodec *codec = NULL; //音频重采样的上下文
    int vpts = 0;
    int apts = 0;
};


XMediaEncode * XMediaEncode::Get(unsigned char index)
{
    static bool isFirst = true;
    if(isFirst)
    {
        //注册所有的编码器
        avcodec_register_all();
        isFirst = false;
    }
    static CXMediaEncode cxm[255];
    return &cxm[index];
}

XMediaEncode::XMediaEncode()
{

}

XMediaEncode::~XMediaEncode()
{

}

CMakeLists.txt


cmake_minimum_required(VERSION 3.1)
project(opencv_example_project)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

set(CMAKE_BUILD_TYPE Debug)

find_package(OpenCV REQUIRED)

message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

find_library(AVCODEC_LIBRARY avcodec)
find_library(AVFORMAT_LIBRARY avformat)
find_library(AVUTIL_LIBRARY avutil)
find_library(AVDEVICE_LIBRARY avdevice)

find_package(Qt6 COMPONENTS Core)
find_package(Qt6 COMPONENTS Gui)
find_package(Qt6 COMPONENTS Multimedia)
find_package(Qt6 COMPONENTS Widgets)


add_executable(qt_audio_rtmp VideoRecordWorker.cpp XMediaEncode.cpp Controller.cpp main.cpp AudioRecordWorker.cpp)

target_link_libraries(qt_audio_rtmp PRIVATE 
    ${OpenCV_LIBS} 
    Qt::Core
    Qt::Gui
    Qt::Multimedia
    Qt::Widgets
    pthread 
    swresample 
    m 
    swscale 
    avformat    
    avcodec 
    avutil 
    avfilter 
    avdevice 
    postproc 
    z 
    lzma  
    rt)

音频推流


xz@xiaqiu:~/study/csdn/rtmp/audio_to_rtmp/qt_audio_rtmp/build$ make
[ 12%] Automatic MOC and UIC for target qt_audio_rtmp
[ 12%] Built target qt_audio_rtmp_autogen
Scanning dependencies of target qt_audio_rtmp
[ 25%] Building CXX object CMakeFiles/qt_audio_rtmp.dir/Controller.cpp.o
[ 37%] Linking CXX executable qt_audio_rtmp
[100%] Built target qt_audio_rtmp
xz@xiaqiu:~/study/csdn/rtmp/audio_to_rtmp/qt_audio_rtmp/build$ 
xz@xiaqiu:~/study/csdn/rtmp/audio_to_rtmp/qt_audio_rtmp/build$ ls
CMakeCache.txt  cmake_install.cmake  Makefile       qt_audio_rtmp_autogen
CMakeFiles      config.tests         qt_audio_rtmp
xz@xiaqiu:~/study/csdn/rtmp/audio_to_rtmp/qt_audio_rtmp/build$ ./qt_audio_rtmp
main thread: 0x7fbcb0d40d80
AudioRecordWorker() thread: 0x7fbcb0d40d80
音频重采样上下文初始化成功
avcodec_alloc_context3 success!
avcodec_open2 success!
Output #0, flv, to 'rtmp://0.0.0.0/live':
    Stream #0:0: Audio: aac (LC), 44100 Hz, stereo, fltp, 40 kb/s
doSomething() "copy" thread: 0x7fbc9dffb700
size =  4096


ffplay rtmp://0.0.0.0/live 测试 nginx-rtmp 音频流

视频


xz@xiaqiu:~/study/csdn/rtmp/audio_to_rtmp/qt_audio_rtmp/build$ ./qt_audio_rtmp
main thread: 0x7f08c167bd80
doSomething() "copy" thread: 0x7f08bd509700
[ WARN:0] global ../modules/videoio/src/cap_gstreamer.cpp (935) open OpenCV | GStreamer warning: Cannot query video position: status=0, value=-1, duration=-1
rtsp://test:test@192.168.1.4cam open success
avcodec_alloc_context3 success!
[libx264 @ 0x7f08ac21dd80] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0x7f08ac21dd80] profile High, level 3.0
[libx264 @ 0x7f08ac21dd80] 264 - core 155 r2917 0a84d98 - H.264/MPEG-4 AVC codec - Copyleft 2003-2018 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=8 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=2 keyint=50 keyint_min=5 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=abr mbtree=1 bitrate=409 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
avcodec_open2 success!
Output #0, flv, to 'rtmp://0.0.0.0/live':
    Stream #0:0: Video: h264, yuv420p, 640x480, q=2-31, 409 kb/s

ffplay rtmp://0.0.0.0/live 测试 nginx-rtmp 视频流

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt6 ffmpeg 音频和视频(非同步)推流到nginx-rtmp 的相关文章

  • 针对初学者的 QT 商业许可证与非商业许可证 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 QT 许可似乎非常反学习 因为据我所知 用它开发的任何东西都只能是商业的当且仅当 its entire开发是在使用商业许可证的情况下完成的
  • Android for OpenCV - 打开跟踪文件时出错,UnsatisfiedLinkError

    我对 Android 开发和 OpenCV 都是新手 我从 Android 下载了 OpenCV 库http sourceforge net projects opencvlibrary files opencv android http
  • python openCV 中的人口普查变换

    我开始在一个与立体视觉相关的项目中使用 openCV 和 python 我找到了关于使用 openCV 在 C 中进行人口普查转换的文档页面 link http docs opencv org 3 1 0 d2 d7f namespacec
  • 连接到 QNetworkReply::error 信号

    我正在使用 Qt5 的新连接语法 QNetworkReply 有一个名为error http qt project org doc qt 5 0 qtnetwork qnetworkreply html error 2还有一个函数叫做err
  • QFileSystemModel setRootPath

    我正在尝试创建一个 Qt 应用程序来显示文件夹 Mac OS 中的 Users 文件夹 的内容 这是代码 QFileSystemModel dirModel new QFileSystemModel dirModel gt setRootP
  • QML 列表视图拖放

    我想创建两个 qml 列表视图 可以执行两个功能 拖放一个列表中的项目以更改项目的顺序 跨列表拖放项目 项目将从一个列表中删除并添加到另一个列表中 根据 Qt 文档中的拖放示例 我决定创建两个访问同一列表模型的列表视图 列表模型中的每个项目
  • 使用 openCV 对图像中的子图像进行通用检测

    免责声明 我是计算机视觉菜鸟 我看过很多关于如何在较大图像中查找特定子图像的堆栈溢出帖子 我的用例有点不同 因为我不希望它是具体的 而且我不确定如何做到这一点 如果可能的话 但我感觉应该如此 我有大量图像数据集 有时 其中一些图像是数据集的
  • Qt 支持 Windows 蓝牙 API 吗?

    谁能告诉我 Qt 是否支持 Windows 蓝牙 API 如果是这样 您能否分享一些有关如何使用它的信息 自上次答复以来 这个问题的答案发生了一些变化 Qt 5 2 版为 Linux BlueZ 和 BlackBerry 设备实现了蓝牙 A
  • Nginx 配置文件在 Elastic Beanstalk 部署期间被覆盖?

    我需要将 p3p 标头添加到标准 Nodejs 和 Nginx Elastic Beanstalk 上的静态资源位置 我创建了一个ebextension脚本如上所解释这个问题 https stackoverflow com question
  • OpenCV:将垫子除以标量的最简单方法是什么

    我认为标题中已经包含了很多内容 显然我可以迭代和划分 但我认为有一种内置的方法 我看见cvConvertScale但这不适用于类型cv Mat 我知道标量乘法的缩放运算 cv Mat M float alpha cv Mat Result
  • 通过单击内部小部件而不是标题栏来移动窗口

    在 Windows 中 当我创建 QMainWindow 时 我可以通过单击标题栏并拖动它来在屏幕上移动它 在我的应用程序中 我使用隐藏了标题栏setWindowFlags Qt CustomizeWindowHint 我正在尝试使用小部件
  • Python 中的 Lanczos 插值与 2D 图像

    我尝试重新缩放 2D 图像 灰度 图像大小为 256x256 所需输出为 224x224 像素值范围从 0 到 1300 我尝试了两种使用 Lanczos 插值来重新调整它们的方法 首先使用PIL图像 import numpy as np
  • 计数物体和更好的填充孔的方法

    我是 OpenCV 新手 正在尝试计算物体的数量在图像中 我在使用 MATLAB 图像处理工具箱之前已经完成了此操作 并在 OpenCV Android 中也采用了相同的方法 第一步是将图像转换为灰度 然后对其进行阈值计算 然后计算斑点的数
  • Nginx 502 网关错误。通过增加buffer来解决。为什么?

    我正在设置 LEMP 堆栈来运行 Drupal 我安装了 Nginx 和 PHP FastCGI Nginx 工作正常 但任何运行 PHP 的尝试都会出现错误 502 Bad Gateway 谷歌很快发现 nginx 502 错误网关 ht
  • 为什么 QT 设计器重新调整大小或不允许我缩小或展开小部件或按钮?

    很多时候 在使用 QT 设计器时 我发现自己需要通过缩小或扩展来调整事物的大小 每当我尝试这样做时 程序都不允许我这样做 而只是恢复到将对象放置在窗口中时给我的原始默认大小 无论我的布局如何 为什么要这样做 是否有可能改变这一点 以便我可以
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • OpenCv 与 Android studio 1.3+ 使用新的 gradle - 未定义的参考

    我在使用原生 OpenCv 2 4 11 3 0 0 也可以 和 Android Studio 1 3 以及新的 ndk 支持时遇到问题 所有关于 mk 文件的教程 但我想将它与新的实验性 gradle 一起使用 使用 Kiran 答案An
  • 在 Qt 中,许多插槽连接到同一信号,它们在发出信号时是否按顺序调用?

    In the Qt文件说 如果多个插槽连接到一个信号 则这些插槽将 按照它们连接的顺序一个接一个地执行 当信号发出时 但在connect 功能 设置Qt ConnectionType输入为Qt QueuedConnection意思是 当控制
  • 在 HSV 颜色空间内定义组织学图像掩模的颜色范围(Python、OpenCV、图像分析):

    为了根据颜色将组织学切片分成多个层 我修改了 OpenCV 社区提供的一些广泛分布的代码 1 我们的染色程序用不同的颜色标记组织横截面的不同细胞类型 B 细胞为红色 巨噬细胞为棕色 背景细胞核为蓝色 I m interested in se
  • QObject 通用信号处理程序

    信号处理程序 是指插槽 而不是 POSIX 信号的处理程序 我需要 连接 可能不会 using QObject connect直接地 所有信号从 QObject 的 未知 子类的实例到一个单槽另一个 QObject 的 我需要这个才能通过网

随机推荐

  • NIO是什么?适用于何种场景?

    NIO与IO的最大区别就是 当读取数据的时候 NIO读取之后需要缓冲 是面向缓冲区的 而IO不需要缓冲 是面向流的 IO是阻塞的 就意味着当一个线程调用read 或write 时 该线程被阻塞 直到有一些数据被读取 或数据完全写入 该线程在
  • 支持STEM学习的九个方式

    随着STEM教育的兴起 一些国家把STEM教育提升到了国家战略层面 相继出台了促进STEM人才培养的政策措施 加大STEM教育的公共和私人投资 整合政府 大中小学 企业 科研机构 社区和家庭多方力量 共同促进STEM教育发展 接下来 格物斯
  • 如何在树莓派上使用Nginx搭建本地站点并通过内网穿透实现远程访问

    文章目录 1 Nginx安装 2 安装cpolar 3 配置域名访问Nginx 4 固定域名访问 5 配置静态站点 安装 Nginx 发音为 engine x 可以将您的树莓派变成一个强大的 Web 服务器 可以用于托管网站或 Web 应用
  • svg转换png,svg转png格式步骤

    svg转换png svg转png格式步骤 在过去一年多的工作经历中 我接触到了大量的图片 认识到了各种图片格式 每种格式图片拥有的属性是不一样的 就像我们每个人所具备的属性性格特点不同一个道理 比如SVG是一种图形文件格式 用户可以直接用代
  • 网络错误代码

    网络错误代码 又称ADSL错误代码 ADSL Asymmetric Digital Subscriber Line 非对称数字用户环路 是中国电信报提供的一种新的数据传输方式 它因为上行和下行带宽不对称 因此称为非对称数字用户线环路 它采用
  • 混淆矩阵的计算方式

    下图中有三个序列 L表示标签值 P表示预测值 n表示分类数 我们需要计算n L P来计算预测结果值 当L和P都取最大时 得出的结果就是其最大计算空间 例如下图 L 0 5 P 0 5 则n L P 0 35 然后我们将n L P映射到36维
  • Linux的环境配置文件----.bashrc文件

    bashrc文件主要保存个人的一些个性化设置 如命令别名 路径等 也即在同一个服务器上 只对某个用户的个性化设置相关 它是一个隐藏文件 需要使用ls a来查看 bash history 记录之前输入的命令 bash logout 当你退出时
  • 南大通用GBase8s 常用SQL语句(256)

    使用 FILE TO 选项 当您执行 SET EXPLAIN FILE TO 语句时 开启说明输出 SET EXPLAIN FILE TO 语句可更改说明输出的缺省的文件名称 直到会话结束为止 或直到发出另一 SET EXPLAIN 语句为
  • vue账号密码登录增加记住密码功能

    实现思路 刷新登录页面时查看cookie中是否存储用户名 密码 是否记住密码 如果有就将cookie中的用户名和密码回显到form表单中 如果没有则将用户输入的用户名和密码存入cookie html代码 只截取了部分账号密码功能部分代码 主
  • 1. 数学导论 - 概述

    文章目录 为什么需要数学 人类如何表示数字 计算机可以做什么 因为部分自媒体上无法显示公式 为了方便 有的地方我是直接整段截图 和文章字体不一致的部分还望见谅 Hi 大家好 又见面了 我是茶桁 这次我依然给大家带来的是基础部分 让我们进入
  • HTTP代理IP使爬虫轻松面对反爬虫

    在数据信息变的越发重要的时候 咱们可以从许多场所去取得数据源 不过要控制好数据抓取的方式 今天介绍一下数据抓取怎么样可以避免出现IP封停问题 先说一下爬虫的分类 爬虫一般分为三类 1 传统爬虫 从一个或若干初始网页的URL开始 取得初始网页
  • EBS销售订单挑库发放处理程序

    在EBS实施中 经常遇到从外部传进来一个被登记的销售订单 需要通过程序进行销售订单的挑库发放 下面是对SO挑库发放的实现步骤的详细实现 1 对销售订单的有效性验证 1 检查销售订单的行是否被完全传回客户化表 2 验证销售订单的关键字段 3
  • CTF之web安全

    web安全 CSRF 简介 CSRF 全名 Cross Site Request Forgery 跨站请求伪造 很容易将它与 XSS 混淆 对于 CSRF 其两个关键点是跨站点的请求与请求的伪造 由于目标站无 token 或 referer
  • 灰度斜坡intensity ramp和灰度台阶intensity step的区别

    在数字图像处理中 锐化处理关注的是灰度变化 discontinuities 的过渡部分 包括灰度台阶和灰度斜坡两种情况的突变 step and ramp discontinuities 那么这二者有什么区别呢 老猿理解 灰度斜坡 inten
  • 终于还是对闲鱼下手了。闲鱼爬虫,idlefish spider来了

    闲鱼目前最大的问题在于没有html请求口子了 闲鱼用了自家的app口子 而且还有spdy协议 拒绝使用代理 如果想采集闲鱼数据 并保存下来 做个对比分析之类的 传统的非传统的招数都已经凉了 怎么说呢 面对闲鱼 你想抓个包都不好抓了 所以 这
  • Win11注册表编辑器误删了如何恢复?

    注册表编辑器是一个用来更改系统注册表设置的高级工具 与资源管理器的界面很类似 近期有用户将注册表编辑器误删了 那么应该如何恢复呢 下面小编就给大家分享一下详细的恢复方法 遇到同样问题的用户注意了 更多重装系统教程尽在小白系统重装官网 1 首
  • 「量化」快乐:UC Berkeley 利用 AI 追踪多巴胺释放量及释放脑区

    内容一览 多巴胺是神经系统中重要的神经递质 与运动 记忆和奖赏系统息息相关 它是快乐的信使 当我们看到令人愉悦的东西时 体内就会分泌多巴胺 诱导我们向它追寻 然而 多巴胺的准确定量分析目前仍难以实现 借助机器学习 美国加利福尼亚大学伯克利分
  • mw325r 服务器无响应),水星(MERCURY)MW325R路由器连不上网怎么办?

    路由器换ip小助手 路由器换ip工具 是一款切换ip软件 专门适用于使用路由器上网的用户 有时候由于ip限制 需要更换上网ip地址 由于使用路由器上网 没有像拨号上网那样方便重新拨号就能换ip 每次需要重启路由器才可以 那这款专门给路由器用
  • SpringBoot+Mybatis多数据源配置

    SpringBoot Mybatis多数据源配置 Step1 在application properties配置两个数据源 数据源1 spring datasource one url jdbc mysql localhost 3306 s
  • Qt6 ffmpeg 音频和视频(非同步)推流到nginx-rtmp

    main cpp include