FFmpeg 获取 rtsp rtmp 流

使用 FFmpeg 获取 rtsp/rtmp 流非常方便,将开发 rtsp/rtmp 客户端工作变的简单了许多。
FFmpeg 获取 rtsp rtmp 流_第1张图片

  1. 将 rtsp/rtmp 流路径送入 avformat_open_input 函数进行打开动作,得到 AVFormatContext 封装格式上下文;
  2. 调用 avformat_find_stream_info 获取流的详细信息;
  3. 分别记录 rtsp/rtmp 流中的音频和视频流索引;
  4. 初始化视频解码器上下文,调用 avcodec_parameters_to_context 将流信息转移到视频解码器上下文中;
  5. 调用 avcodec_find_decoder 得到 AVCodec 视频解码器;
  6. 调用 avcodec_open2 打开视频解码器;
  7. 音频解码器的打开步骤和视频解码器类似;
  8. 初始化音频帧和视频帧,用于获取 rtsp/rtmp 流中的帧数据;
  9. 开启获取 rtsp/rtmp 流线程进行不断轮训获取数据;
  10. 不在使用的时候要关闭打开流时的各种“对象”。

先来看一下头文件,MediaHandler 类是用来处理 rtsp/rtmp 流的封装处理类。

/**
 *    author : liuhongwei
 *    e-mail : 
 *    date   : 2021/6/8 18:17
 *    desc   : FFmpeg 多媒体处理,解复用
 *    version: 1.0
 */

#ifndef MEDIA_MEDIAHANDLER_H
#define MEDIA_MEDIAHANDLER_H

extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}

#include 
#include 
#include "logger.h"

class MediaHandler {
public:
    MediaHandler();

    ~MediaHandler();

    void openStream(char *path, bool is_audio_disable);

    void closeStream();

    void readPacket();

    static void *_readPacket(void *self) {
        static_cast<MediaHandler *>(self)->readPacket();
        return nullptr;
    }

private:
    pthread_t demuxer_thread;
    pthread_mutex_t packet_data_cb_mutex;

    volatile bool is_stream_demuxer;

    AVFormatContext *pFormatCtx;
    AVCodecContext *pVideoCodecCtx;
    AVCodecContext *pAudioCodecCtx;

    int videoWidth;
    int videoHeight;

    int video_stream_idx;
    int audio_stream_idx;

    bool is_audio_disable;

    AVFrame *stream_video_frame;
    AVFrame *stream_audio_frame;
};


#endif //MEDIA_MEDIAHANDLER_H

下面是 rtsp/rtmp 流打开函数,packet_data_cb_mutex 是一个互斥体,用来保证回调数据接口和设置回调对象不产生竞争,此处代码已经删除了设置回调的接口,因此这个互斥体可以去除。由于业务删减,其实代码中的视频解码器相关代码均可删除。

void MediaHandler::openStream(char *path, bool is_audio) {
    pthread_mutex_init(&packet_data_cb_mutex, nullptr);

    is_audio_disable = is_audio;
    //封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息
    pFormatCtx = avformat_alloc_context();

    AVDictionary *options = nullptr;

    av_dict_set(&options, "buffer_size", "1024000", 0);
    av_dict_set(&options, "max_delay", "500000", 0);
    av_dict_set(&options, "stimeout", "3000000", 0);  //设置超时断开连接时间
    av_dict_set(&options, "rtsp_transport", "tcp", 0);  //如果以tcp方式打开将udp替换为tcp

    if (avformat_open_input(&pFormatCtx, path, nullptr, &options) != 0) {
        LOGE("Can not open video: %s", path);
        return;
    }

    if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) {
        LOGE("Can not find video stream info");
        return;
    }

    //获取流的索引位置
    video_stream_idx = -1;
    audio_stream_idx = -1;

    LOGI("stream nums: %d", pFormatCtx->nb_streams);
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        //流的类型
        AVMediaType stream_type = pFormatCtx->streams[i]->codecpar->codec_type;
        if (stream_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_idx = i;
            LOGI("video_stream_idx=%d", video_stream_idx);
        } else if (stream_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_idx = i;
            LOGI("audio_stream_idx=%d", audio_stream_idx);
        }
    }

    if (video_stream_idx == -1) {
        LOGE("Can not find video stream");
        return;
    }

    if (audio_stream_idx == -1) {
        LOGE("Can not find video stream");
        return;
    }

    // 初始化视频解码器上下文
    pVideoCodecCtx = avcodec_alloc_context3(nullptr);
    avcodec_parameters_to_context(pVideoCodecCtx, pFormatCtx->streams[video_stream_idx]->codecpar);
    // 获取视频的宽高
    videoWidth = pFormatCtx->streams[video_stream_idx]->codecpar->width;
    videoHeight = pFormatCtx->streams[video_stream_idx]->codecpar->height;
    LOGI("videoWidth=%d videoHeight=%d\n", videoWidth, videoHeight);
    AVCodec *pVideoCodec = avcodec_find_decoder(pVideoCodecCtx->codec_id);

    if (pVideoCodec == nullptr) {
        LOGE("Can not find video decoder\n");
        return;
    }
    if (avcodec_open2(pVideoCodecCtx, pVideoCodec, nullptr) < 0) {
        LOGE("Video decoder can not open\n");
        return;
    }

    if (is_audio) {
        // 初始化音频解码器上下文
        pAudioCodecCtx = avcodec_alloc_context3(nullptr);
        avcodec_parameters_to_context(pAudioCodecCtx,
                                      pFormatCtx->streams[audio_stream_idx]->codecpar);
        AVCodec *pAudioCodec = avcodec_find_decoder(pAudioCodecCtx->codec_id);

        if (pAudioCodec == nullptr) {
            LOGE("Can not find audio decoder\n");
            return;
        }
        if (avcodec_open2(pAudioCodecCtx, pAudioCodec, nullptr) < 0) {
            LOGE("Audio decoder can not open\n");
            return;
        }
    }

    stream_video_frame = av_frame_alloc();
    stream_audio_frame = av_frame_alloc();

    is_stream_demuxer = true;
    
    pthread_create(&demuxer_thread, nullptr, &MediaHandler::_readPacket, (void *) this);
}

现在来重点看从数据流中获取数据包的方法 readPacket 具体实现。此处视频帧获取后是通过回调接口直接返回的,返回给了硬解码器去解码,相关代码已删除,音频处理如果开启则会直接进行解码处理,实际上视频解码处理流程也是类似的。

  1. 构建 AVPacket;
  2. 调用 av_read_frame 将数据包填充到 AVPacket;
  3. 根据流索引判断是音频流还是视频流;
  4. 视频流经过回调业务处理后,调用 av_packet_free 释放 AVPacket;
  5. 音频流数据包被 avcodec_send_packet 发送到音频解码器后释放 AVPacket,接着根据 avcodec_send_packet 返回值判断是否存在解码成功的音频帧,调用 avcodec_receive_frame 获取音频解码帧。
  6. 下一轮循环继续迭代。
void MediaHandler::readPacket() {
    while (is_stream_demuxer) {
        AVPacket *stream_packet = av_packet_alloc();
        //LOGI("%s 1 stream_packet=%p", __FUNCTION__, stream_packet);
        stream_packet->flags = 0;
        if (av_read_frame(pFormatCtx, stream_packet) >= 0) {
            int result;
            // 视频流
            if (video_stream_idx != -1 && stream_packet->stream_index == video_stream_idx) {
                pthread_mutex_lock(&packet_data_cb_mutex);
                //LOGI("PacketDataCallback->onDataArrived");
                pthread_mutex_unlock(&packet_data_cb_mutex);

                av_packet_free(&stream_packet);
                //LOGI("%s 2 v stream_packet=%p", __FUNCTION__, stream_packet);
            } else if (audio_stream_idx != -1 &&
                       stream_packet->stream_index == audio_stream_idx) {

                if (is_audio_disable) {
                    av_packet_free(&stream_packet);
                    //LOGI("%s 2 a stream_packet=%p", __FUNCTION__, stream_packet);
                    continue;
                }

                result = avcodec_send_packet(pAudioCodecCtx, stream_packet);
                av_packet_free(&stream_packet);
                if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
                    continue;
                } else if (result < 0) {
                    LOGE("Error during decoding audio: avcodec_send_packet");
                    return;
                }
                //LOGI("decoding audio...");
                while (result >= 0) {
                    result = avcodec_receive_frame(pAudioCodecCtx, stream_audio_frame);
                    if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
                        break;
                    } else if (result < 0) {
                        LOGE("Error during decoding audio: avcodec_receive_frame");
                        return;
                    }

                }
            } else {
                av_packet_free(&stream_packet);
            }

        } else {
            //当返回值小于0时,内部会进行缓存并释放,或者不进行缓存,由数据本身损坏或正常结束来决定。
            usleep(10 * 1000);
        }
    }
}

当 rtsp/rtmp 流不再使用的时候关闭并释放资源,防止内存泄漏和出现各种野指针bug。

  1. demuxer_thread 线程结束;
  2. 关闭视频解码器上下文,并释放其占用的内存空间;
  3. 关闭音频解码器上下文,并释放其占用的内存空间;
  4. 关闭封装格式上下文;
  5. 释放视频流帧;
  6. 释放音频流帧。
void MediaHandler::closeStream() {
    is_stream_demuxer = false;
    pthread_join(demuxer_thread, nullptr);
    pthread_mutex_destroy(&packet_data_cb_mutex);

    if (pVideoCodecCtx != nullptr) {
        avcodec_close(pVideoCodecCtx);
        avcodec_free_context(&pVideoCodecCtx);
        pVideoCodecCtx = nullptr;
    }

    if (pAudioCodecCtx != nullptr) {
        avcodec_close(pAudioCodecCtx);
        avcodec_free_context(&pAudioCodecCtx);
        pAudioCodecCtx = nullptr;
    }

    if (pFormatCtx != nullptr) {
        avformat_close_input(&pFormatCtx);
        pFormatCtx = nullptr;
    }

    if (stream_video_frame != nullptr) {
        av_frame_free(&stream_video_frame);
    }

    if (stream_audio_frame != nullptr) {
        av_frame_free(&stream_audio_frame);
    }
}

你可能感兴趣的:(音视频,ffmpeg,rtsp,rtmp,视频,音频)