欧美1区2区3区激情无套,两个女人互添下身视频在线观看,久久av无码精品人妻系列,久久精品噜噜噜成人,末发育娇小性色xxxx

音視頻新手項目-B站本地電影直播(FFmpeg+QT)

1 項目簡介和效果展示

1.1 項目簡介

項目需求:以直播的方式循環(huán)播放一個本地視頻。

開發(fā)環(huán)境:FFmpeg6.0 + QT5.15.2 (QT只是作為IDE工具使用,不涉及QT UI界面)

原始的項目路徑:https://github.com/juniorxsound/libav-RTMP-Streaming.git

注:我這里講解的項目源碼是老廖已經(jīng)修改過的,GitHub上原有的代碼在ffmpeg6.0會報錯。

視頻講解:音視頻新手項目-B站本地電影直播源碼和文檔分享

1.2 效果展示

項目效果演示:可以推到自己的流媒體服務(wù)器,也可以推送到B站直播間

1.2.1 推送到自己的流媒體服務(wù)器

推送到自己流媒體和拉流效果(左邊是推流窗口,右邊是拉流窗口)

1.2.2 推送到B站直播間

2 項目框圖

難點:

  1. 能自己畫出框架圖(不少同學(xué)在學(xué)技術(shù)的時候沒法先把項目的框架圖整理出來);
  2. 如何按正常播放速度推送本地文件;
  3. 解復(fù)用 ->復(fù)用的時間轉(zhuǎn)換。

后續(xù)可以考慮的擴展:

  1. 支持任意格式文件的推流(目前這個程序需要本地文件的視頻/音頻編碼格式符合FLV的要求),如果需要支持任意格式,需要考慮對音頻/視頻進行解碼后再編碼;
  2. 添加文字水印或者圖片水印,此時就必須 重新編碼。

3 項目源碼剖析

我們先分析整體的源碼邏輯,然后再剖析具體函數(shù)的實現(xiàn)

3.1 源碼框架圖

Streamer: C++類,封裝本地文件以RTMP協(xié)議推送到流媒體服務(wù)器

3.2 main函數(shù)入口

main函數(shù)要傳入本地文件路徑以及rtmp推流地址,比如用QT運行該程序時:

完整的main函數(shù)如下所示:

int main(int argc, char *argv[])
{
    int  ret = 0;
    std::cout << std::endl
        << "Welcome to RTMP streamer  " << std::endl
        << "Written by @juniorxsound <https://orfleisher.com>" << std::endl
        << std::endl;
    //檢測參數(shù)輸入情況
    if(argc != 3)
    {
        printf("please input video_file_name rtmp_server_address\n");
    }

    // 獲取本地文件路徑
    char *video_file_name = argv[1];
    // 獲取rtmp推流地址
    char *rtmp_server_address = argv[2];
    // 構(gòu)造一個推流實例
    Streamer streamer(video_file_name, rtmp_server_address);

    //    Streamer streamer("F://0voice//linux_ke//35-demux-decode//time.flv", "rtmp://114.215.169.66/live/darren");
    //初始化 解析本地文件媒體信息  創(chuàng)建輸出流信息
    if(streamer.Init() < 0)
    {
        printf("Streamer Init failed\n");
        return -1;
    }
    //開始推流 和流媒體服務(wù)器交互 讀取本地文件的包 按正常播放速度控制讀取進度 按推流要求轉(zhuǎn)換時間基 發(fā)送給流媒體服務(wù)器
    ret = streamer.Stream();

    #if 0
    //如果需要循環(huán)播放,可以按照以下流程進行
    int loop_count = 0;
    while(true) {
        printf("loop cout -> %d\n", ++loop_count); //打印循環(huán)播放次數(shù)
        ret = streamer.Init();
        if(ret < 0)
        {
            printf("Streamer Init failed\n");
            break;
        }
        ret = streamer.Stream();
        if(ret < 0) {
            printf("Streamer Stream failed\n");
            break;
        }
    }
    #endif
    return ret;
}

3.3 Streamer::Init()初始化函數(shù)

該函數(shù)主要 解析本地文件和創(chuàng)建輸出流信息。

int Streamer::Init()
{
    int ret = 0;
    // 初始化網(wǎng)絡(luò) , 默認狀態(tài)下, FFmpeg是不允許聯(lián)網(wǎng)的 , 必須調(diào)用該函數(shù) , 初始化網(wǎng)絡(luò)后FFmpeg才能進行聯(lián)網(wǎng) 
    ret = avformat_network_init();
    if(ret < 0)
    {
        printf("avformat_network_init failed\n");
        return ret;
    }
    if(!video_file_name_)
    {                 //檢測本地文件名是否為空
        printf("video_file_name is null\n");
        return -1;
    }
    if(!rtmp_server_address_)
    {             //檢測rtmp地址是否為空
        printf("rtmp_server_address is null\n");
        return -1;
    }
    //解析本地文件媒體信息 并獲取視頻流index
    ret = setupInput(video_file_name_);     //打開本地文件
    if (ret < 0)
    {
        printf("setupInput failed\n");
        return ret;
    }
    // 創(chuàng)建輸出流信息  對應(yīng)的編碼信息來自于     本地文件媒體信息
    ret = setupOutput(rtmp_server_address_);    //創(chuàng)建輸出信息
    if (ret < 0)
    {
        printf("setupOutput failed\n");
        return ret;
    }
    return 0;
}

3.3.1 Streamer::setupInput()解析本地文件函數(shù)

這里之所以獲取video index,目的是用來判斷讀取本地文件的packet是否為視頻包,需要根據(jù)視頻包的時間戳控制推流速度。

int Streamer::setupInput(const char *video_file_name)
{
    int ret = 0;
    // 解析本地文件媒體信息
    ret = avformat_open_input(&ifmt_ctx_, video_file_name, NULL, NULL);
    if (ret < 0)
    {
        printf("Could not open input file.");
        return -1;
    }
    ret = avformat_find_stream_info(ifmt_ctx_, NULL);
    if (ret < 0)
    {
        printf("Failed to retrieve input stream information");
        return -1;
    }
    // 獲取視頻流video_index
    for (uint32_t i = 0; i < ifmt_ctx_->nb_streams; i++)
    {
        if (ifmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_index_ = i;  //找到視頻索引的目的是為了在推流時按正常播放速率推送
            break;
        }
    }

    av_dump_format(ifmt_ctx_, 0, video_file_name, 0);
    return 0;
}

3.3.2 Streamer::setupOutput()創(chuàng)建輸出流信息函數(shù)

int Streamer::setupOutput(const char *_rtmp_server_address)
{
    int ret = 0;
    ret = avformat_alloc_output_context2(&ofmt_ctx_, NULL, "flv", _rtmp_server_address); //RTMP
    if (ret < 0)
    {
        printf("Could not create output context\n");
        return -1;
    }

    ofmt_ = (AVOutputFormat *)ofmt_ctx_->oformat;
    for (uint32_t i = 0; i < ifmt_ctx_->nb_streams; i++)
    {
        //Create output AVStream according to input AVStream
        AVStream *in_stream = ifmt_ctx_->streams[i];
        // 創(chuàng)建輸出流信息
        AVStream *out_stream = avformat_new_stream(ofmt_ctx_, NULL);
        if (!out_stream)
        {
            printf("Failed allocating output stream\n");
            return -1;
        }
        //對應(yīng)的編碼信息來自于本地文件媒體信息 Copy the settings of AVCodecParameters
        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if (ret < 0)
        {
            printf("Failed to copy parameters from input to output stream codec parameters\n");
            return -1;
        }
        out_stream->codecpar->codec_tag = 0;
    }
    av_dump_format(ofmt_ctx_, 0, _rtmp_server_address, 1);
    return 0;
}

3.4 Streamer::Stream()推流函數(shù)

這里重點在于時間基的轉(zhuǎn)換,需要將輸入流的時間基轉(zhuǎn)成輸出流的時間基:

in_stream = ifmt_ctx_->streams[pkt_->stream_index];
        out_stream = ofmt_ctx_->streams[pkt_->stream_index];
        pkt_->pts = av_rescale_q_rnd(pkt_->pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt_->dts = av_rescale_q_rnd(pkt_->dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt_->duration = av_rescale_q(pkt_->duration, in_stream->time_base, out_stream->time_base);

難點在于推流速度的控制,需要以正常播放速度控制推流速度

//控制讀取進度
if (pkt_->stream_index == video_index_)
{
    AVRational time_base = ifmt_ctx_->streams[video_index_]->time_base; //獲取本地文件視頻流的時間基
    AVRational time_base_q = {1, AV_TIME_BASE};
    // 這里存在bug的可能,因為不是所有文件的音視頻流都是從0開始的,更正確的做法是要減去 ifmt_ctx_->start_time,但要判斷其是不是AV_NOPTS_VALUE
    // 但不做處理大部分的視頻文件也是沒有問題的
    int64_t pts_time = av_rescale_q(pkt_->dts, time_base, time_base_q); //將dts轉(zhuǎn)成微妙(us)的單位
    int64_t now_time = av_gettime() - start_time_;      //計算 開始推流時間 到當(dāng)前時間 經(jīng)過的時間
    if (pts_time > now_time)                        //如果要推送的 數(shù)據(jù) 快于 正常播放時間 則休眠
        av_usleep(pts_time - now_time);
    if(now_time - print_last_time > print_interval) {
        printf("play time: %0.3fs\n", now_time/1000000.0); //轉(zhuǎn)成秒的單位
        print_last_time = now_time;
    }
}

完整的Stream()函數(shù)代碼:

int Streamer::Stream()
{
    int ret = 0;
    //Open output URL
    if (!(ofmt_->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx_->pb, rtmp_server_address_, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("Could not open output URL '%s'", rtmp_server_address_);
            return -1;
        }
    }
    //和流媒體服務(wù)器交互 Write file header
    ret = avformat_write_header(ofmt_ctx_, NULL);
    if (ret < 0)
    {
        printf("Error occurred when opening output URL\n");
        return -1;
    }

    pkt_ = av_packet_alloc();
    start_time_ = av_gettime();       //這個單位是us
    int64_t print_interval = 2*1000*1000;   //單位是us,2*1000*1000us = 2*1000ms = 2s
    int64_t print_last_time = 0;
    while (1)
    {
        AVStream *in_stream, *out_stream;
        //讀取本地文件的包
        ret = av_read_frame(ifmt_ctx_, pkt_);
        if (ret < 0) {
            printf("av_read_frame have error or end of file\n");
            break;
        }
        if (pkt_->pts == AV_NOPTS_VALUE)
        {
            //Write PTS
            AVRational time_base1 = ifmt_ctx_->streams[video_index_]->time_base;
            //Duration between 2 frames (us)
            int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ifmt_ctx_->streams[video_index_]->r_frame_rate);
            //Parameters
            pkt_->pts = (double)(frame_index_ * calc_duration) / (double)(av_q2d(time_base1) * AV_TIME_BASE);
            pkt_->dts = pkt_->pts;
            pkt_->duration = (double)calc_duration / (double)(av_q2d(time_base1) * AV_TIME_BASE);
        }
        //控制讀取進度
        if (pkt_->stream_index == video_index_)
        {
            AVRational time_base = ifmt_ctx_->streams[video_index_]->time_base;
            AVRational time_base_q = {1, AV_TIME_BASE};
            int64_t pts_time = av_rescale_q(pkt_->dts, time_base, time_base_q);
            int64_t now_time = av_gettime() - start_time_;
            if (pts_time > now_time)
                av_usleep(pts_time - now_time);
            if(now_time - print_last_time > print_interval) {
                printf("play time: %0.3fs\n", now_time/1000000.0); //轉(zhuǎn)成秒的單位
                print_last_time = now_time;
            }
        }

        //按推流要求轉(zhuǎn)換時間基
        in_stream = ifmt_ctx_->streams[pkt_->stream_index];
        out_stream = ofmt_ctx_->streams[pkt_->stream_index];
        pkt_->pts = av_rescale_q_rnd(pkt_->pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt_->dts = av_rescale_q_rnd(pkt_->dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt_->duration = av_rescale_q(pkt_->duration, in_stream->time_base, out_stream->time_base);
        pkt_->pos = -1;
        if (pkt_->stream_index == video_index_)
        {
            frame_index_++;
        }
        ret = av_write_frame(ofmt_ctx_, pkt_);
//        ret = av_interleaved_write_frame(ofmt_ctx_, pkt_);

        if (ret < 0)
        {
            printf("Error muxing packet\n");
            break;
        }
    }

    //Write file trailer
    av_write_trailer(ofmt_ctx_);
    avformat_close_input(&ifmt_ctx_);
    if (ofmt_ctx_ && !(ofmt_->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx_->pb);
    av_packet_free(&pkt_);
    avformat_free_context(ofmt_ctx_);
    if (ret < 0 && ret != AVERROR_EOF)
    {
        printf("Error occurred.\n");
        return -1;
    }
    return 0;
}

4 項目展望

該項目的目的是掌握解復(fù)用/復(fù)用/時間基轉(zhuǎn)換,以及如何控制推流速度,但這個項目還不足以支撐找工作,切記。

PS: 后續(xù)可以考慮增加文字水印或者圖片水印,這樣你的直播影院更有標識度。

#實習(xí)##校招##C++##簡歷中的項目經(jīng)歷要怎么寫##牛客創(chuàng)作賞金賽#
全部評論

相關(guān)推薦

評論
點贊
4
分享

創(chuàng)作者周榜

更多
??途W(wǎng)
??推髽I(yè)服務(wù)