FFmpeg 获取 rtsp rtmp 流

使用 FFmpeg 获取 rtsp/rtmp 流非常方便,将开发 rtsp/rtmp 客户端工作变的简单了许多。
在这里插入图片描述

  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_Hextern "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替换为tcpif (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);}
}