近期因为项目上安卓平台上要对接摄像头流,但是使用ffmpeg软解码,消耗CPU性能较大;
所以使用ffmpeg解复用rtsp流,取到ES数据后使用硬件解码。
特意抽了一个demo出来,希望能帮到大家,特别简单,一开始想复杂了。
注:假如是其他格式的流或者文件类似,相应的修改open_input_file()就好了,网上有很多,这就以rtsp流为例。
解析MP4文件的话注意下h264_mp4toannexb。
下面直接上代码:
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavutil/mathematics.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/audio_fifo.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfiltergraph.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libavutil/audio_fifo.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
}
#endif
#include <string>
#include <map>
using namespace std;
#ifdef WIN32
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")
#endif
static int g_nFFmpegInitFlag = 0;
AVFormatContext *m_ifmt_ctx;
AVFormatContext *m_ofmt_ctx;
int m_nInputVideoIndex; // 输入视频流下标
int m_nInputAudioIndex; // 输入音频流下标
string m_strStreamUrl;
void InitFfmpeg()
{
if (!g_nFFmpegInitFlag)
{
av_register_all();
avfilter_register_all();
avformat_network_init();
g_nFFmpegInitFlag = 1;
}
}
void ReleaseFfmpeg()
{
int i = 0;
if (m_ifmt_ctx != NULL)
{
for (i = 0; i < m_ifmt_ctx->nb_streams; i++)
{
if (m_ifmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
avcodec_close(m_ifmt_ctx->streams[i]->codec);
}
avformat_close_input(&m_ifmt_ctx);
}
if (m_ofmt_ctx != NULL)
{
for (i = 0; i < m_ofmt_ctx->nb_streams; i++)
{
if (m_ofmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
avcodec_close(m_ofmt_ctx->streams[i]->codec);
}
avformat_free_context(m_ofmt_ctx);
}
}
int open_input_file()
{
int ret = -1;
unsigned int i;
m_ifmt_ctx = NULL;
m_ofmt_ctx = NULL;
AVInputFormat* in_fmt = NULL;
AVDictionary* options = NULL;
// 指定网卡
//av_dict_set(&options, "localaddr", "172.17.36.15", 0);
// 设置超时,udp:10秒 rtsp:
if (m_strStreamUrl.find("udp://") != string::npos || m_strStreamUrl.find("UDP://") != string::npos)
{
av_dict_set(&options, "timeout", to_string(10000000).c_str(), 0);
}
else if (m_strStreamUrl.find("rtsp://") != string::npos)
{
char option_key[] = "rtsp_transport";
char option_value[] = "tcp";
av_dict_set(&options, option_key, option_value, 0);
av_dict_set(&options, "stimeout", to_string(10 * 1000000).c_str(), 0);
}
// 此处解码传入rtsp地址,由ffmpeg自己收流解码
if ((ret = avformat_open_input(&m_ifmt_ctx, m_strStreamUrl.c_str(), NULL, &options)) < 0)
{
printf("Cannot open input file\n");
return ret;
}
if ((ret = avformat_find_stream_info(m_ifmt_ctx, NULL)) < 0)
{
printf("Cannot find stream information\n");
return ret;
}
/* select the audio stream */
AVCodec *dec;
ret = av_find_best_stream(m_ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
if (ret < 0)
{
printf("Cannot find a audio stream in the input file\n");
return ret;
}
for (i = 0; i < m_ifmt_ctx->nb_streams; i++)
{
if (m_ifmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO && m_ifmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
AVStream *stream = m_ifmt_ctx->streams[i];
dec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!dec)
{
printf("Failed to find decoder for stream %d\n", i);
return AVERROR_DECODER_NOT_FOUND;
}
AVCodecContext *codec_ctx;
codec_ctx = stream->codec;
if (!codec_ctx)
{
printf("Fail to allocate the decoder context");
return AVERROR(ENOMEM);
}
ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
if (ret < 0)
{
return ret;
}
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
/* Open decoder */
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
m_nInputVideoIndex = i;
codec_ctx->framerate = av_guess_frame_rate(m_ifmt_ctx, stream, NULL);
}
else
{
m_nInputAudioIndex = i;
}
ret = avcodec_open2(codec_ctx, dec, NULL);
if (ret < 0)
{
return ret;
}
}
}
return 0;
}
int main()
{
FILE* fp = fopen("1.es", "ab+");
m_strStreamUrl = "rtsp://192.168.15.170:554/1";
InitFfmpeg();
open_input_file();
int nRet = 0;
AVPacket packet = { 0 };
if ((nRet = open_input_file()) < 0)
{
printf("open_input_file error\n");
goto end;
}
// 只读1000帧
int nCount = 0;
while (1000 != nCount)
{
if ((nRet = av_read_frame(m_ifmt_ctx, &packet)) < 0)
{
av_packet_unref(&packet);
// 读取失败,重新获取
continue;
}
//packet.data存放的就是对应的ES数据
fwrite(packet.data, packet.size, 1, fp);
fflush(fp);
av_packet_unref(&packet);
nCount++;
}
end:
ReleaseFfmpeg();
if (fp!=NULL)
{
fclose(fp);
fp = NULL;
}
system("pause");
return 0;
}