FFmpeg 获取 rtsp rtmp 流

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

将 rtsp/rtmp 流路径送入 avformat_open_input 函数进行打开动作,得到 AVFormatContext 封装格式上下文;

调用 avformat_find_stream_info 获取流的详细信息;

分别记录 rtsp/rtmp 流中的音频和视频流索引;

初始化视频解码器上下文,调用 avcodec_parameters_to_context 将流信息转移到视频解码器上下文中;

调用 avcodec_find_decoder 得到 AVCodec 视频解码器;

调用 avcodec_open2 打开视频解码器;

音频解码器的打开步骤和视频解码器类似;

初始化音频帧和视频帧,用于获取 rtsp/rtmp 流中的帧数据;

开启获取 rtsp/rtmp 流线程进行不断轮训获取数据;

不在使用的时候要关闭打开流时的各种“对象”。

先来看一下头文件,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 <pthread.h>
#include <unistd.h>
#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 具体实现。此处视频帧获取后是通过回调接口直接返回的,返回给了硬解码器去解码,相关代码已删除,音频处理如果开启则会直接进行解码处理,实际上视频解码处理流程也是类似的。

构建 AVPacket;

调用 av_read_frame 将数据包填充到 AVPacket;

根据流索引判断是音频流还是视频流;

视频流经过回调业务处理后,调用 av_packet_free 释放 AVPacket;

音频流数据包被 avcodec_send_packet 发送到音频解码器后释放 AVPacket,接着根据 avcodec_send_packet 返回值判断是否存在解码成功的音频帧,调用 avcodec_receive_frame 获取音频解码帧。

下一轮循环继续迭代。

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 流

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!以下是一个简单的示例代码,用于将RTSP转发到RTMP服务器: ```cpp #include <iostream> #include <cstdlib> #include <cstring> #include <unistd.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> int main(int argc, char* argv[]) { // 注册所有的FFmpeg组件 av_register_all(); // 创建输入上下文 AVFormatContext* inputContext = avformat_alloc_context(); // 打开RTSP if (avformat_open_input(&inputContext, "rtsp://your_rtsp_url", nullptr, nullptr) != 0) { std::cerr << "无法打开RTSP" << std::endl; return -1; } // 查找信息 if (avformat_find_stream_info(inputContext, nullptr) < 0) { std::cerr << "无法获取信息" << std::endl; return -1; } // 创建输出上下文 AVFormatContext* outputContext = avformat_alloc_context(); // 设置输出格式为RTMP AVOutputFormat* outputFormat = av_guess_format("flv", nullptr, nullptr); outputContext->oformat = outputFormat; // 打开输出URL if (avio_open(&outputContext->pb, "rtmp://your_rtmp_url", AVIO_FLAG_WRITE) < 0) { std::cerr << "无法打开RTMP URL" << std::endl; return -1; } // 遍历输入 for (unsigned int i = 0; i < inputContext->nb_streams; i++) { AVStream* inputStream = inputContext->streams[i]; AVStream* outputStream = avformat_new_stream(outputContext, inputStream->codec->codec); // 复制参数 if (avcodec_copy_context(outputStream->codec, inputStream->codec) < 0) { std::cerr << "无法复制参数" << std::endl; return -1; } outputStream->codec->codec_tag = 0; if (outputContext->oformat->flags & AVFMT_GLOBALHEADER) { outputStream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } } // 写入输出头部 if (avformat_write_header(outputContext, nullptr) < 0) { std::cerr << "无法写入输出头部" << std::endl; return -1; } // 转发数据 AVPacket packet; while (av_read_frame(inputContext, &packet) >= 0) { AVStream* inputStream = inputContext->streams[packet.stream_index]; AVStream* outputStream = outputContext->streams[packet.stream_index]; // 设置时间基 av_packet_rescale_ts(&packet, inputStream->time_base, outputStream->time_base); packet.pos = -1; // 写入输出 if (av_interleaved_write_frame(outputContext, &packet) < 0) { std::cerr << "无法写入输出" << std::endl; break; } av_packet_unref(&packet); } // 写入输出尾部 av_write_trailer(outputContext); // 清理资源 avformat_close_input(&inputContext); avio_close(outputContext->pb); avformat_free_context(outputContext); return 0; } ``` 请注意,这只是一个简单的示例代码,用于演示如何使用FFmpegRTSP转发到RTMP服务器。你需要根据自己的需求进行适当的修改和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值