借助局域网的强大带宽,需要开发一款医学教学的app。大概的功能需求包括播放各种视频,播放各种摄像头,跨平台android,linux,mac,win等,支持输出左右眼双目,裸眼3d,红蓝3d,普通播放等功能,还需要对一些视频中的重要特征做标注。我立刻就瞄上了ffmpeg和opencv,一个用来解码,一个用来处理适时实时帧,堪称量身定做。
brew安装过程非常简单。只是在安装ffmpeg时出错了,ffplay一直装不上,后来下载源码编译成功。
ffmpeg安装成功后,可以使用命令测试,可在单机测试,我主要在局域网测试,没有去配置rstp服务器了,就直接使用udp测试。
推送视频流:
ffmpeg -re -i ~/work/VideoTest1.mp4 -vcodec copy -f mpegts udp://127.0.0.1:8000
使用ffplay播放视频流
ffplay -protocol_whitelist "file,udp,rtp" -i udp://127.0.0.1:8000
推送摄像头数据流:
ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 -i "0" -vcodec libx264 -acodec libfaac -f mpegts udp://127.0.0.1:8000
使用ffplay播放视频流
ffplay -protocol_whitelist "file,udp,rtp" -i udp://127.0.0.1:8000
推送桌面流,这个可以用于企业分享PPT之类
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f mpegts udp://127.0.0.1:8000
使用ffplay播放视频流
ffplay -protocol_whitelist "file,udp,rtp" -i udp://127.0.0.1:8000
写一个简单的解码测试DEMO,解码视频帧。
#include <iostream>
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
int main(int argc, const char * argv[]) {
// insert code here...
avcodec_register_all();
av_register_all();
avformat_network_init();
//输出支持解封装格式
printf("====== av_input_format =====\n");
AVInputFormat *fmt = NULL;
while ((fmt = av_iformat_next(fmt))) {
printf("name : %s\n",fmt->name);
printf("long_name : %s\n",fmt->long_name);
printf("\n");
}
printf("==============================\n");
AVFormatContext *pAVFormatCtx = NULL;
pAVFormatCtx = avformat_alloc_context();
//char filepath[] = "/Users/jiazhiguo/work/VideoTest1.mp4";
if(argc<2)
{
//ShowUseage(argv[0]);
return -1;
}
const char *filepath = argv[1];
//打开文件
char errorBuf[1024];
int retOpenFile = avformat_open_input(&pAVFormatCtx, filepath, NULL, NULL);
if (0 != retOpenFile){
av_strerror(retOpenFile, errorBuf, sizeof(errorBuf));
printf("Couldn't open file %s: %d(%s)\n", filepath, retOpenFile, errorBuf);
return -1;
}
//输出文件信息
printf("------------- File Information ------------------\n");
av_dump_format(pAVFormatCtx,0,filepath,0);
printf("-------------------------------------------------\n");
//音视频分离
int retFindStream = avformat_find_stream_info(pAVFormatCtx, NULL);
if (0 != retFindStream){
av_strerror(retFindStream, errorBuf, sizeof(errorBuf));
printf("Couldn't find stream %s: %d(%s)\n", filepath, retFindStream, errorBuf);
return -1;
}
int videoStreamIndex = -1;
for (int i = 0; i < pAVFormatCtx->nb_streams;i++){
AVStream *stream = pAVFormatCtx->streams[i];
AVCodecParameters *codeParam = stream->codecpar;
if (AVMEDIA_TYPE_VIDEO == codeParam->codec_type){
videoStreamIndex = i;
break;
}
}
if (-1 == videoStreamIndex){
printf("Didn't find a video stream.\n");
return -1;
}
//视频流信息
AVStream *videoStream = pAVFormatCtx->streams[videoStreamIndex];
AVCodecParameters *codeParam = videoStream->codecpar;
AVCodecContext *pAVCodeCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pAVCodeCtx, codeParam);
if (0 == pAVCodeCtx){
printf("Couldn't create AVCodecContext\n");
return -1;
}
//查找视频解码器
AVCodecID videoCodeId = codeParam->codec_id;
AVCodec *videoDeCode = avcodec_find_decoder(videoCodeId);
if(videoDeCode == NULL){
printf("Codec not found.\n");
return -1;
}
//打开视频解码器
int retOpenVideoDecode = avcodec_open2(pAVCodeCtx, videoDeCode, NULL);
if (retOpenVideoDecode != 0){
av_strerror(retOpenVideoDecode, errorBuf, sizeof(errorBuf));
printf("open decode Error. %s\n",errorBuf);
return -1;
}
AVPacket *avPacket = av_packet_alloc();
AVFrame *avVideoFrame = av_frame_alloc();
bool bFirstFrame = false;
while (1) {
//从原始数据读取一帧
av_read_frame(pAVFormatCtx, avPacket);
if (avPacket->stream_index == videoStreamIndex){
//送往解码器
int retPackt = avcodec_send_packet(pAVCodeCtx, avPacket);
if (retPackt < 0){
av_strerror(retPackt, errorBuf, sizeof(errorBuf));
printf("packet Error. %s\n",errorBuf);
continue;
}
//从解码器获取一帧
int retDcode = avcodec_receive_frame(pAVCodeCtx, avVideoFrame);
if(retDcode < 0){
av_strerror(retDcode, errorBuf, sizeof(errorBuf));
printf("Decode Error. %s\n",errorBuf);
continue;
}else{
bFirstFrame = true;
break;
}
}
}
if (bFirstFrame){
//todo 图像处理
}
//资源释放
av_frame_free(&avVideoFrame);
av_packet_free(&avPacket);
avcodec_close(pAVCodeCtx);
avformat_close_input(&pAVFormatCtx);
avformat_network_deinit();
return 0;
}
写一个ffmpeg的makefile文件
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.c = .o)
CC = gcc
INCLUDES = -I/usr/local/Cellar/ffmpeg/4.2.2_2/include
LIBS = -L/usr/local/Cellar/ffmpeg/4.2.2_2/lib -lavformat -lavcodec -lswscale -lswresample -lavutil -lavfilter -lavdevice -lpostproc
LIBS += -lm -lz -lbz2 -llzma -pthread
CCFLAGS = -g -Wall -O0
my_app : $(OBJS)
$(CC) $^ -o $@ $(INCLUDES) $(LIBS)
%.o : %.c
$(CC) -c $< $(CCFLAGS)
clean:
rm *.o
.PHONY:clean
执行make,生成app
my_app 输入视频文件解码
参考:
FFMPEG demo参考;
https://blog.csdn.net/zgq57609356/article/details/65443197
https://blog.csdn.net/m0_37684310/article/details/101776778
makefile参考:
http://blog.chinaunix.net/uid-25838286-id-3204219.html
https://blog.csdn.net/weixin_38391755/article/details/80380786