Qt+FFmpeg は VLC を模倣して RTSP ストリームを受信して再生します

この記事では、参照用に実行可能なソース コードを提供します。

この記事の特典として、無料の C++ オーディオおよびビデオ学習教材パッケージ + 学習ルートの概要、技術ビデオ/コード (オーディオおよびビデオの開発、面接の質問、FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、コーデック、プッシュプルストリーミング、SRS)↓↓↓以下からご覧ください↓↓記事下部をクリックして無料で入手してください↓↓

効果

 

RTSPストリームを生成する

ファイルの再生よりも少し複雑なのは、RTSP ストリームを受信するために RTSP ストリームを生成する必要があることです。RTSP ストリーミング環境を構築するだけです。

EasyDarwin を使用して、RTSP サービスを RTSP サーバーとして有効にします。

ffmpeg コマンド ラインをクライアントとして使用し、ループ内でビデオ ファイルを EasyDarwin にプッシュします。

./ffmpeg.exe -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://127.0.0.1/stream

これにより、EasyDarwin から RTSP ストリームを受信できるようになります。

vlc を使って RTSP ストリームを受信して​​見てみましょう。

正常に受信されました。

FFmepg が RTSP ストリーム コードを受信する

FFmpeg を使用して RTSP ストリームを受信して​​再生するプロセスは、mp4 ファイルを再生するプロセスと似ていますが、mp4 ファイルを再生する場合はファイルが再生ソースとして使用され、RTSP ストリームを受信する場合は RTSP ストリームが再生ソースとして使用される点が異なります。プレイソース:

引き続き、プロセス内のキー コードを見てみましょう。

if (avformat_open_input(&fileFmtCtx, url.toStdString().c_str(), nullptr, nullptr) != 0) {
    qDebug() << "avformat_open_input() failed";
    return;
}

RTSP アドレスを開くために使用されます。ファイルを開く場合と比較して、ストリーム情報を見つける必要があるだけでなく、RTSP サーバーとの接続を確立し、RTSP サーバーがストリームのプッシュを開始できるようにする必要もあります。

上記の RTSP ストリームを受信した後、AVFormatContext の関連プロパティを出力します。

qDebug() << "stream name: " << streamFmtCtx->url;
    qDebug() << "stream iformat: " << streamFmtCtx->iformat->name;
    qDebug() << "stream duration: " << streamFmtCtx->duration << " microseconds";
    qDebug() << "stream bit_rate: " << streamFmtCtx->bit_rate;
/* 
stream name:  rtsp://127.0.0.1/stream
stream iformat:  rtsp
stream duration:  -9223372036854775808  microseconds
stream bit_rate:  0
*/

今回はRTSPストリームなので正確な長さは取得できません。ストリーム関連情報の出力を続行します。

qDebug() << "nb_streams:";
    for (unsigned int i = 0; i < streamFmtCtx->nb_streams; i++) {
        AVStream *stream = streamFmtCtx->streams[i];
        qDebug() << "Stream " << i + 1 << ":";
        qDebug() << "  Codec: " << avcodec_get_name(stream->codecpar->codec_id);
        qDebug() << "  Duration: " << stream->duration << " microseconds";
    }
/*
nb_streams:
Stream  1 :
  Codec:  h264
  Duration:  -9223372036854775808  microseconds
Stream  2 :
  Codec:  aac
  Duration:  -9223372036854775808  microseconds
*/

前回ファイルを直接読み取ったときと同じ結果 (1 つの H264 ビデオ ストリームと 1 つの AAC オーディオ ストリームを含む) が表示されます。

swsCtx = sws_getContext(decoderCtx->width, decoderCtx->height, decoderCtx->pix_fmt,
                                     decoderCtx->width, decoderCtx->height, FMT_PIC_SHOW,
                                     SWS_BICUBIC, NULL, NULL, NULL);
qDebug() << "decoderCtx->pix_fmt:" << av_get_pix_fmt_name(decoderCtx->pix_fmt);
//decoderCtx->pix_fmt: yuv420p

sws_getContext()は、RTSPストリーム形式を表示形式(ここではyuv420p=>AV_PIX_FMT_RGB24)に変換するために使用されます。

int numBytes = av_image_get_buffer_size(FMT_PIC_SHOW, decoderCtx->width, decoderCtx->height, 1);
showBuffer = (unsigned char*)av_malloc(static_cast<unsigned long long>(numBytes) * sizeof(unsigned char));
if(av_image_fill_arrays(showFrame->data, showFrame->linesize,
                        showBuffer, FMT_PIC_SHOW, decoderCtx->width, decoderCtx->height, 1) < 0)
{
    qDebug() << "av_image_fill_arrays() failed";
    return;
}

av_image_get_buffer_size は、画像データを計算するためのバッファ サイズを計算します。av_malloc は、showBuffer に 1 つのメモリ ブロックを割り当てます。av_image_fill_arrays は、AVFrame のデータ メンバーとラインサイズ メンバーを画像パラメーターと showBuffer で初期化し、AVFrame を showBuffer に関連付けます。

while(av_read_frame(streamFmtCtx, packet) >= 0){
    if(packet->stream_index == nVideoIndex){
        if(avcodec_send_packet(decoderCtx, packet)>=0){
            while((ret = avcodec_receive_frame(decoderCtx, decodedFrame)) >= 0){
                //...
            }
        }
    }
}

デコード手順は mp4 ファイルの再生と同様で、RTSP ストリームからデータ パケット AVPacket を読み取り、デコードのために AVPacket をデコーダに送信し、デコーダからデコードされたビデオ フレームの受信を試み、受信したフレーム データを decodedFrame に保存します。

上記の基本的な手順を完了すると、コードは RTSP サーバーから RTSP ストリームを受信し、VLC のように再生できるようになります。

RTSPプロトコルの簡単な説明と検証

FFmpeg は内部で RTSP 接続の確立を非常にうまく処理しますが、RTSP プロトコルについてさらに学習する必要があります。RTSP の正式名称は Real Time Sreaming Protocol で、TCP/IP プロトコル システムのアプリケーション層プロトコルです。データ送信は RTP/RTCP によって完了し、最下層は TCP/UDP によって実装されます。

標準の RTSP ストリーミング プロトコル層の対話プロセスは次のとおりです。

言うことはあまりありませんが、上記のプッシュ環境を直接使用し (EasyDarwin が特定の情報を暗号化するようだったので、別の RTSP サーバーを選択しました。効果は同じです)、VLC を使用してストリームを収集し、Wireshark を使用してパケットをキャプチャして確認します。契約プロセスは次のようになりますか?

各情報が何であるかを直接見てみましょう。

クライアント => サーバー

Real Time Streaming Protocol
    Request: OPTIONS rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: OPTIONS
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 2\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    \r\n

クライアントは OPTIONS を rtsp://127.0.0.1:554/stream に送信して、サーバーがどの RTSP メソッドをサポートするかを尋ねます。

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    CSeq: 2\r\n
    Session: 4J_bOCNSg
    Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD\r\n
    \r\n

サーバー応答は DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE、OPTIONS、ANNOUNCE、RECORD をサポートします

クライアント => サーバー

Real Time Streaming Protocol
    Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: DESCRIBE
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 3\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Accept: application/sdp\r\n
    \r\n

クライアントは、application/sdp 形式のメディア記述ファイルを要求します。

通常、サーバーはユーザー認証を実行します。認可認証情報が伝送されていない場合、または認証が失敗した場合、サーバーはエラー番号 401 の応答を返します。クライアントは 401 応答を受信すると、認証情報に基づいて認可を生成する必要があります。既知のユーザ認証情報を送信して DESCRIBE を送信し、認証が成功すると、サーバは SDP を付加した応答情報を返します。

認証を行うかどうかはRTSPサーバーに依存しますが、ここではEasyDarwinには認証を設定していません。

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
    CSeq: 3\r\n
    Session: _ZLZ7_NSR
    Content-type: application/sdp
    Content-length: 511
    \r\n
    Session Description Protocol
        Session Description Protocol Version (v): 0
        Owner/Creator, Session Id (o): - 0 0 IN IP4 127.0.0.1
        Session Name (s): No Name
        Connection Information (c): IN IP4 127.0.0.1
        Time Description, active time (t): 0 0
        Session Attribute (a): tool:libavformat 58.76.100
        Media Description, name and address (m): video 0 RTP/AVP 96
        Bandwidth Information (b): AS:1894
        Media Attribute (a): rtpmap:96 H264/90000
        Media Attribute (a): fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAKqwspQFAFumoCAgKAAADAAIAAAMAYcTAAc/YABW+f4xwEA==,aOkJNSU=; profile-level-id=64002A
        Media Attribute (a): control:streamid=0
        Media Description, name and address (m): audio 0 RTP/AVP 97
        Bandwidth Information (b): AS:317
        Media Attribute (a): rtpmap:97 MPEG4-GENERIC/48000/2
        Media Attribute (a): fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1190
        Media Attribute (a): control:streamid=1

サーバーは SDP 情報を返し、現在利用可能なオーディオおよびビデオのストリームと属性をクライアントに伝えます。SDP プロトコルは拡張されていません。ここで注意する必要があるより重要な情報は、サーバーが streamid=0 の H264 ビデオ ストリームと streamid=1 の AAC オーディオ ストリームを送信できることです。

クライアント => サーバー

Real Time Streaming Protocol
    Request: SETUP rtsp://127.0.0.1:554/stream/streamid=0 RTSP/1.0\r\n
        Method: SETUP
        URL: rtsp://127.0.0.1:554/stream/streamid=0
    CSeq: 4\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Transport: RTP/AVP;unicast;client_port=52024-52025
    \r\n

クライアントは SETUP を送信して、ビデオ ストリームである streamid=0 の接続を確立する必要があることをサーバーに伝えます。ここで、RTP/AVP は UDP 経由の送信を意味し、unicast はユニキャストを意味し、client_port=52024-52025 については別途説明する必要があります。前述したように、RTSP プロトコル データは、RTP+ RTCP を介して送信されます。RTP と RTCP はどちらも UDP 上に構築されており、RTP はデフォルトで偶数のポート番号を使用しますが、RTCP はデフォルトで RTP ポートの次の奇数のポート番号 (ここでは 52024 と 52025) を使用します。

サーバー => クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    CSeq: 4\r\n
    Session: 4J_bOCNSg
    Transport: RTP/AVP;unicast;client_port=52024-52025
    \r\n

サーバーはクライアントに確認を返します。

クライアント => サーバー

Real Time Streaming Protocol
    Request: SETUP rtsp://127.0.0.1:554/stream/streamid=1 RTSP/1.0\r\n
        Method: SETUP
        URL: rtsp://127.0.0.1:554/stream/streamid=1
    CSeq: 5\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Transport: RTP/AVP;unicast;client_port=52028-52029
    Session: 4J_bOCNSg
    \r\n

クライアントはサーバーに、streamid=1 のオーディオ ストリームへの接続を確立する必要があることを伝えます。RTP と RTCP のポートはそれぞれ 52028 と 52029 です。

サーバー => クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    Transport: RTP/AVP;unicast;client_port=52028-52029
    CSeq: 5\r\n
    Session: 4J_bOCNSg
    \r\n

サーバーはクライアントに確認を返します。

クライアント=>サーバー

Real Time Streaming Protocol
    Request: PLAY rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: PLAY
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 6\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Session: 4J_bOCNSg
    Range: npt=0.000-\r\n
    \r\n

クライアントは PLAY を送信してサーバーに送信を開始するように指示します。範囲はメディアの再生時間を表します。サーバーは範囲の値に従って、指定されたセグメントのデータ ストリームを再生します。リアルタイム ストリームの場合、通常は開始点のみが指定されますつまり、範囲: npt=0.000-

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    CSeq: 6\r\n
    Session: 4J_bOCNSg
    Range: npt=0.000-\r\n
    \r\n

サーバーは確認を返し、同じセッションを使用します。

クライアント=>サーバー

Real Time Streaming Protocol
    Request: TEARDOWN rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: TEARDOWN
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 7\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Session: 4J_bOCNSg
    \r\n

クライアントは TEARDOWN を送信してストリーミング停止リクエストを開始します。

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    CSeq: 7\r\n
    Session: 4J_bOCNSg
    \r\n

サーバーは確認を返し、同じセッションを使用し、ストリーミングを停止します。

ダイジェスト認証環境を構築する

上で述べたように、サーバーがユーザー認証を実行する可能性があるため、認証が必要な環境を作成する必要があります。EasyDarwin が認証を直接選択して easydarwin.ini を開くことができるかどうかを確認してみましょう。

[http]
port=10008
default_username=admin
default_password=admin
#...
;是否使能向服务器推流或者从服务器播放时验证用户名密码. [注意] 因为服务器端并不保存明文密码,所以推送或者播放时,客户端应该输入密码的md5后的值。
;password should be the hex of md5(original password)
authorization_enable=0
#...

authorization_enable 変数が認証を制御していることがわかります。その値を 1 に変更し、サービスを再起動します。この時点で、元の ffmpeg コマンドがストリームのプッシュに失敗していることがわかりました。

つまり、EasyDarwin にプッシュする際にも認証が必要になります。コメントから判断すると、ユーザー名とパスワードの md5 値を追加する必要があります。正しいパラメーターを使用してストリームをプッシュします (以下の mad5ofpassword はパスワードの md5 に置き換えられます)。

./ffmpeg.exe -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://admin:[email protected]/stream

出来た:


このとき、vlc を使って受信してみると、案の定、認証が必要となり、ユーザー名とパスワードが要求されます。


ここでのパスワードには、md5 の後の値も入力する必要があることに注意してください。正しいパスワードを入力すると、vlc は RTSP ストリームを受信できるようになります。


同様に、Wireshark を使用してパケットをキャプチャし、認証プロセスがどのようなものかを確認します。


クライアント=>サーバー

Real Time Streaming Protocol
    Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: DESCRIBE
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 6\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Accept: application/sdp\r\n
    \r\n

まず、クライアントも DESCRIBE を開始します。

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 401 Unauthorized\r\n
        Status: 401
    CSeq: 6\r\n
    Session: ayQBojNIg
    WWW-Authenticate: Digest realm="EasyDarwin", nonce="539c6afee35b8edd354e983a6af947bf", algorithm="MD5"\r\n
    \r\n

WWW-Authenticate: Digest は、ダイジェスト認証が必要であることを示します。応答の生成にはレルムとノンスが使用されます。algorithm="MD5" は、応答の生成に md5 アルゴリズムが必要であることを示します。

クライアント=>サーバー

Real Time Streaming Protocol
    Request: DESCRIBE rtsp://127.0.0.1:554/stream RTSP/1.0\r\n
        Method: DESCRIBE
        URL: rtsp://127.0.0.1:554/stream
    CSeq: 7\r\n
    Authorization: Digest username="admin", realm="EasyDarwin", nonce="539c6afee35b8edd354e983a6af947bf", uri="rtsp://127.0.0.1:554/stream", response="d6a48b37f2010b3ddfad1eef18692648"\r\n
    User-Agent: LibVLC/3.0.18 (LIVE555 Streaming Media v2016.11.28)\r\n
    Accept: application/sdp\r\n
    \r\n

クライアントは対応するアルゴリズムを使用してレスポンスを生成し、サーバーに返します (レスポンスの計算方法については別途説明します)。

サーバー=>クライアント

Real Time Streaming Protocol
    Response: RTSP/1.0 200 OK\r\n
        Status: 200
    Content-length: 511
    CSeq: 7\r\n
    Session: ayQBojNIg
    \r\n
    Data (511 bytes)

サーバーは応答が合格したことを確認し、200 を返します。

実際には、上記のように SDP 情報 (Data 511 バイトの情報) がここに返されますが、EasyDarwin がそれを暗号化しているのか、それとも別の何かで暗号化されているのかを解析できません。

以降の処理はダイジェスト認証を行わない場合と同様です。

ダイジェスト認証を処理するようにコードを改善する

認証が存在する可能性があるため、サーバーに認証がある状況をコードで処理する必要があります。そうしないと、RTSP ストリームを受信できなくなります。まず、サーバーの戻り値がキャプチャされた場所を特定します。いくつかの試みの後、メソッド avformat_open_input で次のことがわかりました。

if ((ret = avformat_open_input(&streamFmtCtx, url.toStdString().c_str(), nullptr, nullptr)) != 0) {
    qDebug() << "ret:" << ret;
}
//打印输出
//ret: -825242872
//ffmpeg日志输出
//[rtsp @ 000001d2d3940ec0] method DESCRIBE failed: 401 Unauthorized

認証が必要な場合、avformat_open_input は負の数を直接返します。ffmpeg のログと組み合わせると、サーバーが Unauthorized を返した場合の状況であると大まかに結論付けることができます。ただし、より具体的な確認が必要なので、avformat_open_input の宣言を見てください。

//avformat.h
/*
* @return 0 on success, a negative AVERROR on failure.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

戻り値は int で、失敗した場合は AVERROR が返されるとコメントに記載されているので、ffmpeg のソース コードにアクセスして AVERROR に関する情報を見つけることができます。

ffmpeg ソース コードをコンパイルすると、それを直接デバッグして、最終的にどのように返されるかを確認できます。しかし、ここではソース コードのコンパイルに余分な時間を費やしたくないため、世界初の IDE、Visual Studio を使用します。 ffmpeg ソース コード フォルダーに移動し、AVERROR を直接検索すると、AVERROR の定義を簡単に見つけることができます。

//error.h
#define AVERROR(e) (-(e))   ///< Returns a negative error code from a POSIX error code, to return from library functions.

AVERROR は、POSIX の標準エラーの逆の数値を取得するために使用されるマクロであることがわかります。追跡を続けても、関連する戻り値は見つかりませんでした。ただし、ヘッダー ファイルには Unauthorized の関連する定義が含まれています。

//error.h
#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
//common.h
#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))

定義によれば、AVERROR_HTTP_UNAUTHORIZED は実際には (0xF8,'4','0','1') の組み合わせのシフトであり、定義に従って計算すると、AVERROR_HTTP_UNAUTHORIZED は実際に -825242872 に等しくなります。確認するには、ffmpeg ソース コードからマクロ定義をコピーし、プロジェクトに直接出力します。

//mainwindow.h
#define AVERROR_HTTP_UNAUTHORIZED  FFERRTAG(0xF8,'4','0','1')
#define MKTAG(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))
#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
//mainwindow.cpp
qDebug() << "AVERROR_HTTP_UNAUTHORIZED:" <<FFERRTAG(0xF8,'4','0','1');

//输出
//AVERROR_HTTP_UNAUTHORIZED: -825242872

出力は前のログ出力と計算結果と同じであり、この時点では、AVERROR_HTTP_UNAUTHORIZED エラーが確実に報告されています。error.h 内の他のマクロ定義を簡単に出力できます。一般的な ffmpeg エラー コードのエラー コード テーブルは次のとおりです。

エラーコードマクロ定義

エラーコード

エラーの説明

AVERROR_BSF_NOT_FOUND

-1179861752

ビットストリームフィルターが見つかりません

AVERROR_BUG

-558323010

内部バグ。AVERROR_BUG2 も参照

AVERROR_BUFFER_TOO_SMALL

-1397118274

バッファが小さすぎます

AVERROR_DECODER_NOT_FOUND

-1128613112

デコーダが見つかりません

AVERROR_DEMUXER_NOT_FOUND

-1296385272

デマルチプレクサが見つかりません

AVERROR_ENCODER_NOT_FOUND

-1129203192

エンコーダが見つかりません

AVERROR_EOF

-541478725

ファイルの終わり

AVERROR_EXIT

-1414092869

即時退出が要求されました。呼び出された関数を再起動しないでください

AVERROR_EXTERNAL

-542398533

外部ライブラリの一般的なエラー

AVERROR_FILTER_NOT_FOUND

-1279870712

フィルターが見つかりません

AVERROR_INVALIDDATA

-1094995529

入力の処理中に無効なデータが見つかりました

AVERROR_MUXER_NOT_FOUND

-1481985528

マクサーが見つかりません

AVERROR_OPTION_NOT_FOUND

-1414549496

オプションが見つかりません

AVERROR_PATCHWELCOME

-1163346256

FFmpeg にはまだ実装されていません。パッチは歓迎です

AVERROR_PROTOCOL_NOT_FOUND

-1330794744

プロトコルが見つかりません

AVERROR_STREAM_NOT_FOUND

-1381258232

ストリームが見つかりません

AVERROR_BUG2

-541545794

AVERROR_UNKNOWN

-1313558101

AVERROR_EXPERIMENTAL

-733130664

AVERROR_INPUT_CHANGED

-1668179713

AVERROR_OUTPUT_CHANGED

-1668179714

AVERROR_HTTP_BAD_REQUEST

-808465656

AVERROR_HTTP_UNAUTHORIZED

-825242872

AVERROR_HTTP_FORBIDDEN

-858797304

AVERROR_HTTP_NOT_FOUND

-875574520

AVERROR_HTTP_OTHER_4XX

-1482175736

AVERROR_HTTP_SERVER_ERROR

-1482175992

したがって、コードに不正な状況の処理を追加することができ、不正な場合、ユーザーはユーザー名とパスワードの入力を求められます。

//ffmpegmanager.cpp
if ((ret = avformat_open_input(&streamFmtCtx, url.toStdString().c_str(), nullptr, nullptr)) != 0) {
    if (ret == AVERROR_HTTP_UNAUTHORIZED)
    {
        //...
        return;
    }else{
        //...
        return;
    }
}

vlc では、入力されたユーザー名とパスワードが検証に合格できない場合、入力が正しいか入力がキャンセルされるまで、検証ボックスが再度ポップアップ表示されます (ユーザー名を再入力する必要はありません) (詳細については冒頭を参照)の効果)。したがって、RTSP アドレスの有効性をチェックするなどの操作も追加します。

//ffmpegmanager.cpp
int rtspIndex = url.indexOf("rtsp://");
int atIndex = url.lastIndexOf("@");
if(rtspIndex != -1 && atIndex != -1){
    QString couple = url.mid(rtspIndex + 7, atIndex - rtspIndex - 7);
    username = couple;
    if(couple.contains(':')){
        username = couple.mid(0, couple.lastIndexOf(':'));
    }
}

この時点で、コードはダイジェスト認証が必要な状況に適応できます。

エラーウィンドウを追加

vlc は、RTSP アドレスを開けない場合、エラー ウィンドウをポップアップ表示します。

また、エラー ウィンドウを追加し、すべてのエラーをアドレスを開けないものとして分類し、印刷します。

メモリリークを修正する

最後に、プログラムは RTSP ストリームを正常に受信できるようになりますが、これまでにないことが起こり、メモリが増加し続けます。この場合、通常、メモリ リークが発生します。以前に MP4 ファイルを読み取ったときにはメモリ リークが見つかりませんでした。ファイル サイズが固定されており、現在はストリーミングされ続けているためである可能性があります。この現象はより明白であり、次のことを行う必要があります。コードを確認してください。簡単に場所を特定した結果、次のコード ブロックが漏洩したことが判明しました。

while(av_read_frame(streamFmtCtx, packet) >= 0){
    if(packet->stream_index == nVideoIndex){
        if(avcodec_send_packet(decoderCtx, packet)>=0){
            while((ret = avcodec_receive_frame(decoderCtx, decodedFrame)) >= 0){
                //...
            }
        }
    }
}

次に、文ごとに調査し、まず av_read_frame の宣言を確認します。

//avformat.h
/**
 *.....
 * On success, the returned packet is reference-counted (pkt->buf is set) and
 * valid indefinitely. The packet must be freed with av_packet_unref() when
 * it is no longer needed. 
 *.....
 */
int av_read_frame(AVFormatContext *s, AVPacket *pkt);

ここで役立つ情報をいくつか紹介します。pkt は参照カウントされており、av_packet_unref() を実行しない場合は永久に有効になります。定義の確認を続けて、私たちの目標は、pkt に関連する参照カウントされたステートメントを見つけることです。

//avformat.cpp
int av_read_frame(AVFormatContext *s, AVPacket *pkt){
    //...
    ret = read_frame_internal(s, pkt);
    ret = avpriv_packet_list_put(&s->internal->packet_buffer,
                                 &s->internal->packet_buffer_end,
                                 pkt, NULL, 0);
    //...
}

最終的に、pkt はこれら 2 つの関数を実行します。探しているのは avpriv_packet_list_put です。引き続きその宣言と定義を見てみましょう。

//packet_internal.h
/**
 * Append an AVPacket to the list.
 *
 * @param head  List head element
 * @param tail  List tail element
 * @param pkt   The packet being appended. The data described in it will
 *              be made reference counted if it isn't already.
 */
int avpriv_packet_list_put(PacketList **head, PacketList **tail,
                           AVPacket *pkt,
                           int (*copy)(AVPacket *dst, const AVPacket *src),
                           int flags);
//avpacket.c
int avpriv_packet_list_put(PacketList **packet_buffer,
                           PacketList **plast_pktl,
                           AVPacket      *pkt,
                           int (*copy)(AVPacket *dst, const AVPacket *src),
                           int flags)
{
    //...
    if (*packet_buffer)
        (*plast_pktl)->next = pktl;
    else
        *packet_buffer = pktl;
    *plast_pktl = pktl;
    return 0;
}

最後に、pkt がバッファリングされたパケットに追加されます。他の詳細を調べる必要はありませんが、pkt がリストに追加されるため、ここで実際にメモリ リークが発生することだけを理解する必要があります。前のステートメントのヒントによると、av_packet_unref() を使用して pkt の参照を解放し、1 つの AVPacket を読み取って使用して終了した後、直接 av_packet_unref() を呼び出す必要があります。

while(av_read_frame(streamFmtCtx, packet) >= 0){
    //...
    av_packet_unref(packet);
}
av_packet_unref(packet);

追加後、メモリリークの問題は解決したことがわかったので、これ以上の調査は続けません。

この記事の特典として、無料の C++ オーディオおよびビデオ学習教材パッケージ + 学習ルートの概要、技術ビデオ/コード (オーディオおよびビデオの開発、面接の質問、FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、コーデック、プッシュプルストリーミング、SRS)↓↓↓以下からご覧ください↓↓記事下部をクリックして無料で入手してください↓↓

おすすめ

転載: blog.csdn.net/m0_73443478/article/details/135200662