国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
FFmpeg流媒體處理

本文為作者原創(chuàng),轉(zhuǎn)載請注明出處:https://www.cnblogs.com/leisure_chn/p/10623968.html


本文為作者原創(chuàng),轉(zhuǎn)載請注明出處:https://www.cnblogs.com/leisure_chn/p/10623968.html

1. 簡介

流媒體是使用了流式傳輸?shù)亩嗝襟w應用技術。如下是維基百科關于流媒體概念的定義:

流媒體 (streaming media) 是指將一連串的媒體數(shù)據(jù)壓縮后,經(jīng)過網(wǎng)絡分段發(fā)送數(shù)據(jù),在網(wǎng)絡上即時傳輸影音以供觀賞的一種技術與過程,此技術使得數(shù)據(jù)包得以像流水一樣發(fā)送;如果不使用此技術,就必須在使用前下載整個媒體文件。

關于流媒體的基礎概念,觀止云的 “流媒體|從入門到出家” 系列文章極具參考價值,請參考本文第 5 節(jié)參考資料部分。

1.1 FFmpeg 影音處理的層次

FFmpeg 中對影音數(shù)據(jù)的處理,可以劃分為協(xié)議層、容器層、編碼層與原始數(shù)據(jù)層四個層次:

協(xié)議層:提供網(wǎng)絡協(xié)議收發(fā)功能,可以接收或推送含封裝格式的媒體流。協(xié)議層由 libavformat 庫及第三方庫(如 librtmp)提供支持。

容器層:處理各種封裝格式。容器層由 libavformat 庫提供支持。

編碼層:處理音視頻編碼及解碼。編碼層由各種豐富的編解碼器(libavcodec 庫及第三方編解碼庫(如 libx264))提供支持。

原始數(shù)據(jù)層:處理未編碼的原始音視頻幀。原始數(shù)據(jù)層由各種豐富的音視頻濾鏡(libavfilter 庫)提供支持。

本文提及的收流與推流的功能,屬于協(xié)議層的處理。

FFmpeg 中 libavformat 庫提供了豐富的協(xié)議處理及封裝格式處理功能,在打開輸入/輸出時,F(xiàn)Fmpeg 會根據(jù) 輸入 URL / 輸出 URL 探測輸入/輸出格式,選擇合適的協(xié)議和封裝格式。例如,如果輸出 URL 是 "rtmp://192.168.0.104/live",那么 FFmpeg 打開輸出時,會確定使用 rtmp 協(xié)議,封裝格式為 flv。

FFmpeg 中打開輸入/輸出的內(nèi)部處理細節(jié)用戶不必關注,因此本文流處理的例程和前面轉(zhuǎn)封裝的例程非常相似,不同之處主要在于輸入/輸出 URL 形式不同,若 URL 攜帶 "rtmp://"、"rpt://"、"udp://"等前綴,則表示涉及流處理;否則,處理的是本地文件。

1.2 流媒體系統(tǒng)中的角色

流媒體系統(tǒng)是一個比較復雜的系統(tǒng),簡單來說涉及三個角色:流媒體服務器、推流客戶端和收流客戶端。推流客戶端是內(nèi)容生產(chǎn)者,收流客戶端是內(nèi)容消費者。示意圖如下:

1.3 收流與推流

如果輸入是網(wǎng)絡流,輸出是本地文件,則實現(xiàn)的是收流功能,將網(wǎng)絡流存儲為本地文件,如下:

如果輸入是本地文件,輸出是網(wǎng)絡流,則實現(xiàn)的是推流功能,將本地文件推送到網(wǎng)絡,如下:

如果輸入是網(wǎng)絡流,輸出也是網(wǎng)絡流,則實現(xiàn)的是轉(zhuǎn)流功能,將一個流媒體服務器上的流推送到另一個流媒體服務器,如下:

2. 源碼

源碼和轉(zhuǎn)封裝例程大部分相同,可以認為是轉(zhuǎn)封裝例程的增強版:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198#include <stdbool.h>#include <libavutil/timestamp.h>#include <libavformat/avformat.h>// ffmpeg -re -i tnhaoxc.flv -c copy -f flv rtmp://192.168.0.104/live// ffmpeg -i rtmp://192.168.0.104/live -c copy tnlinyrx.flv// ./streamer tnhaoxc.flv rtmp://192.168.0.104/live// ./streamer rtmp://192.168.0.104/live tnhaoxc.flvint main(int argc, char **argv){    AVOutputFormat *ofmt = NULL;    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;    AVPacket pkt;    const char *in_filename, *out_filename;    int ret, i;    int stream_index = 0;    int *stream_mapping = NULL;    int stream_mapping_size = 0;    if (argc < 3) {        printf("usage: %s input output\n"               "API example program to remux a media file with libavformat and libavcodec.\n"               "The output format is guessed according to the file extension.\n"               "\n", argv[0]);        return 1;    }    in_filename  = argv[1];    out_filename = argv[2];    // 1. 打開輸入    // 1.1 讀取文件頭,獲取封裝格式相關信息    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {        printf("Could not open input file '%s'", in_filename);        goto end;    }        // 1.2 解碼一段數(shù)據(jù),獲取流相關信息    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {        printf("Failed to retrieve input stream information");        goto end;    }    av_dump_format(ifmt_ctx, 0, in_filename, 0);    // 2. 打開輸出    // 2.1 分配輸出ctx    bool push_stream = false;    char *ofmt_name = NULL;    if (strstr(out_filename, "rtmp://") != NULL) {        push_stream = true;        ofmt_name = "flv";    }    else if (strstr(out_filename, "udp://") != NULL) {        push_stream = true;        ofmt_name = "mpegts";    }    else {        push_stream = false;        ofmt_name = NULL;    }    avformat_alloc_output_context2(&ofmt_ctx, NULL, ofmt_name, out_filename);    if (!ofmt_ctx) {        printf("Could not create output context\n");        ret = AVERROR_UNKNOWN;        goto end;    }    stream_mapping_size = ifmt_ctx->nb_streams;    stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));    if (!stream_mapping) {        ret = AVERROR(ENOMEM);        goto end;    }    ofmt = ofmt_ctx->oformat;    AVRational frame_rate;    double duration;    for (i = 0; i < ifmt_ctx->nb_streams; i++) {        AVStream *out_stream;        AVStream *in_stream = ifmt_ctx->streams[i];        AVCodecParameters *in_codecpar = in_stream->codecpar;        if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&            in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&            in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {            stream_mapping[i] = -1;            continue;        }        if (push_stream && (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) {            frame_rate = av_guess_frame_rate(ifmt_ctx, in_stream, NULL);            duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);        }        stream_mapping[i] = stream_index++;        // 2.2 將一個新流(out_stream)添加到輸出文件(ofmt_ctx)        out_stream = avformat_new_stream(ofmt_ctx, NULL);        if (!out_stream) {            printf("Failed allocating output stream\n");            ret = AVERROR_UNKNOWN;            goto end;        }        // 2.3 將當前輸入流中的參數(shù)拷貝到輸出流中        ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);        if (ret < 0) {            printf("Failed to copy codec parameters\n");            goto end;        }        out_stream->codecpar->codec_tag = 0;    }    av_dump_format(ofmt_ctx, 0, out_filename, 1);    if (!(ofmt->flags & AVFMT_NOFILE)) {    // TODO: 研究AVFMT_NOFILE標志        // 2.4 創(chuàng)建并初始化一個AVIOContext,用以訪問URL(out_filename)指定的資源        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);        if (ret < 0) {            printf("Could not open output file '%s'", out_filename);            goto end;        }    }    // 3. 數(shù)據(jù)處理    // 3.1 寫輸出文件頭    ret = avformat_write_header(ofmt_ctx, NULL);    if (ret < 0) {        printf("Error occurred when opening output file\n");        goto end;    }    while (1) {        AVStream *in_stream, *out_stream;        // 3.2 從輸出流讀取一個packet        ret = av_read_frame(ifmt_ctx, &pkt);        if (ret < 0) {            break;        }        in_stream  = ifmt_ctx->streams[pkt.stream_index];        if (pkt.stream_index >= stream_mapping_size ||            stream_mapping[pkt.stream_index] < 0) {            av_packet_unref(&pkt);            continue;        }        int codec_type = in_stream->codecpar->codec_type;        if (push_stream && (codec_type == AVMEDIA_TYPE_VIDEO)) {            av_usleep((int64_t)(duration*AV_TIME_BASE));        }        pkt.stream_index = stream_mapping[pkt.stream_index];        out_stream = ofmt_ctx->streams[pkt.stream_index];        /* copy packet */        // 3.3 更新packet中的pts和dts        // 關于AVStream.time_base(容器中的time_base)的說明:        // 輸入:輸入流中含有time_base,在avformat_find_stream_info()中可取到每個流中的time_base        // 輸出:avformat_write_header()會根據(jù)輸出的封裝格式確定每個流的time_base并寫入文件中        // AVPacket.pts和AVPacket.dts的單位是AVStream.time_base,不同的封裝格式AVStream.time_base不同        // 所以輸出文件中,每個packet需要根據(jù)輸出封裝格式重新計算pts和dts        av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);        pkt.pos = -1;        // 3.4 將packet寫入輸出        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);        if (ret < 0) {            printf("Error muxing packet\n");            break;        }        av_packet_unref(&pkt);    }    // 3.5 寫輸出文件尾    av_write_trailer(ofmt_ctx);end:    avformat_close_input(&ifmt_ctx);    /* close output */    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) {        avio_closep(&ofmt_ctx->pb);    }    avformat_free_context(ofmt_ctx);    av_freep(&stream_mapping);    if (ret < 0 && ret != AVERROR_EOF) {        printf("Error occurred: %s\n", av_err2str(ret));        return 1;    }    return 0;}

2.1 收流

收流的代碼與打開普通文件的代碼沒有區(qū)別,打開輸入時,F(xiàn)Fmpeg 能識別流協(xié)議及封裝格式,根據(jù)相應的協(xié)議層代碼來接收流,收到流數(shù)據(jù)去掉協(xié)議層后得到的數(shù)據(jù)和普通文件內(nèi)容是一樣的,后續(xù)的處理流程也就一樣了。

2.2 推流

推流有兩個需要注意的地方。

一是需要根據(jù)輸出流協(xié)議顯式指定輸出 URL 的封裝格式:

123456789101112131415    bool push_stream = false;    char *ofmt_name = NULL;    if (strstr(out_filename, "rtmp://") != NULL) {        push_stream = true;        ofmt_name = "flv";    }    else if (strstr(out_filename, "udp://") != NULL) {        push_stream = true;        ofmt_name = "mpegts";    }    else {        push_stream = false;        ofmt_name = NULL;    }    avformat_alloc_output_context2(&ofmt_ctx, NULL, ofmt_name, out_filename);

這里只寫了兩種。rtmp 推流必須推送 flv 封裝格式,udp 推流必須推送 mpegts 封裝格式,其他情況就當作是輸出普通文件。這里使用 push_stream 變量來標志是否使用推流功能,這個標志后面會用到。

二是要注意推流的速度,不能一股腦將收到的數(shù)據(jù)全推出去,這樣流媒體服務器承受不住??梢园匆曨l播放速度(幀率)來推流。因此每推送一個視頻幀,要延時一個視頻幀的時長。音頻流的數(shù)據(jù)量很小,可以不必關心此問題。

在打開輸入 URL 時,獲取視頻幀的持續(xù)時長:

123456    AVRational frame_rate;    double duration;    if (push_stream && (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) {        frame_rate = av_guess_frame_rate(ifmt_ctx, in_stream, NULL);        duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);    }

在 av_read_frame() 之后,av_interleaved_write_frame() 之前增加延時,延時時長就是一個視頻幀的持續(xù)時長:

1234    int codec_type = in_stream->codecpar->codec_type;    if (push_stream && (codec_type == AVMEDIA_TYPE_VIDEO)) {        av_usleep((int64_t)(duration*AV_TIME_BASE));    }

3. 驗證

3.1 編譯第三方庫 librtmp

FFmpeg 默認并不支持 rtmp 協(xié)議。需要先編譯安裝第三方庫 librtmp,然后開啟 --enable-librtmp 選項重新編譯安裝 FFmpeg。具體方法參考:"FFmpeg 開發(fā)環(huán)境構(gòu)建"

3.2 搭建流媒體服務器

測試收流與推流功能需要搭建流媒體服務器。我們選用 nginx-rtmp 作為流媒體服務器用于測試。nginx-rtmp 服務器運行于虛擬機上,推流客戶端與收流客戶端和 nginx-rtmp 服務器處于同一局域網(wǎng)即可。我的虛擬機是 OPENSUSE LEAP 42.3,IP 是 192.168.0.104(就是 nginx-rtmp 服務器的地址)。

為避免搭建服務器的繁瑣過程,我們直接使用 docker 拉取一個 nginx-rtmp 鏡像。步驟如下:

[1] 安裝與配置docker服務

安裝 docker:

sudo zypper install docker

將當前用戶添加到 docker 組(若 docker 組不存在則先創(chuàng)建),從而可以免 sudo 使用 docker 命令:

sudo gpasswd -a ${USER} docker

[2] 配置鏡像加速

docker 鏡像源位于美國,摘取鏡像非常緩慢??膳渲脟鴥?nèi)鏡像源,加快鏡像拉取速度。

修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 鍵值:

123456789{    "registry-mirrors":    [        "https://registry.docker-cn.com",        "https://docker.mirrors.ustc.edu.cn",        "https://hub-mirror.c.163.com",        "https://mirror.ccs.tencentyun.com"    ]}

上述配置文件添加了四個國內(nèi)鏡像源:docker 中國、清華、163 和騰訊。

修改配置文件后重啟 docker 服務:

systemctl restart docker

[3] 拉取 nginx-rtmp 鏡像

docker pull tiangolo/nginx-rtmp

[4] 打開容器

docker run -d -p 1935:1935 --name nginx-rtmp tiangolo/nginx-rtmp

[5] 防火墻添加例外端口

如果無法推流,應在防火墻中將 1935 端口添加例外,修改 /etc/sysconfig/SuSEfirewall2 文件,在 FW_SERVICES_EXT_TCP 項中添加 1935 端口,如下:

FW_SERVICES_EXT_TCP="ssh 1935"

然后重啟防火墻:

systemctl restart SuSEfirewall2

[6] 驗證服務器

測試文件下載(右鍵另存為):tnhaoxc.flv

ffmpeg 推流測試:

ffmpeg -re -i tnhaoxc.flv -c copy -f flv rtmp://192.168.0.104/live

"-re":按視頻幀率的速度讀取輸入
"-c copy":輸出流使用和輸入流相同的編解碼器
"-f flv":指定輸出流封裝格式為flv

ffplay 收流播放測試:

ffplay rtmp://192.168.0.104/live

ffplay 播放正常,說明 nginx-rtmp 流媒體服務器搭建成功,可正常使用。

3.3 編譯

在 shell 中運行如下命令下載例程源碼:

svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_stream

在源碼目錄執(zhí)行 ./compile.sh 命令,生成 streamer 可執(zhí)行文件。

3.4 驗證

測試文件下載(右鍵另存為):shifu.mkv,將測試文件保存在和源碼同一目錄。

推流測試:

./streamer shifu.mkv rtmp://192.168.0.104/live

使用 vlc 播放器打開網(wǎng)絡串流,輸入流地址 "rtmp://192.168.0.104/live",播放正常。上述測試命令等價于:

ffmpeg -re -i shifu.mkv -c copy -f flv rtmp://192.168.0.104/live

收流測試:先按照上一步命令啟動推流,然后運行如下命令收流

./streamer rtmp://192.168.0.104/live shifu.ts

以上測試命令等價于:

ffmpeg -i rtmp://192.168.0.104/live -c copy shifu.ts

接收結(jié)束后檢查一下生成的本地文件 shifu.ts 能否正常播放。

4. 遺留問題

推流的問題:不管是用 ffmpeg 命令,還是用本測試程序,推流結(jié)束時會打印如下信息

1234[flv @ 0x22ab9c0] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properlyLarger timestamp than 24-bit: 0xffffffc2[flv @ 0x22ab9c0] Failed to update header with correct duration.[flv @ 0x22ab9c0] Failed to update header with correct filesize.

收流的問題:推流結(jié)束后,收流超時未收以數(shù)據(jù),會打印如下信息后程序退出運行

RTMP_ReadPacket, failed to read RTMP packet header

5. 參考資料

[1] 雷霄驊, RTMP流媒體技術零基礎學習方法
[2] 觀止云, 【流媒體|從入門到出家】:流媒體原理(上)
[3] 觀止云, 【流媒體|從入門到出家】:流媒體原理(下)
[4] 觀止云, 【流媒體|從入門到出家】:流媒體系統(tǒng)(上)
[5] 觀止云, 【流媒體|從入門到出家】:流媒體系統(tǒng)(下)
[6] 觀止云, 總結(jié):從一個直播APP看流媒體系統(tǒng)的應用

6. 修改記錄

2019-03-29 V1.0 初稿

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
最簡單的基于FFmpeg的推流器(以推送RTMP為例)
??礡TSP流轉(zhuǎn)RTMP并推送至Web端展示
CentOS6下基于Nginx搭建mp4/flv流媒體服務器(可隨意拖動)并支持RTMP/HLS協(xié)議(含轉(zhuǎn)碼工具)
??禂z像頭開發(fā)SRS+ffmpeg+http
srs之深入淺出看流媒體
H5直播起航
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服