使用 FFmpeg 获取 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
#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 具体实现。此处视频帧获取后是通过回调接口直接返回的,返回给了硬解码器去解码,相关代码已删除,音频处理如果开启则会直接进行解码处理,实际上视频解码处理流程也是类似的。
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。
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);
}
}