基本想法:最近看同事将android手机的视频流推到了服务器上,感觉挺有意思的,逐决定学习一下~
下载官方提供好的FFMPEG:https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z
1、解压之后,添加bin路径到window10的环境变量中~
C:\Users\sxj>ffmpeg
ffmpeg version 4.4-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 10.2.0 (Rev6, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-shared --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
libpostproc 55. 9.100 / 55. 9.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
Use -h to get full help or, even better, run 'man ffmpeg'
2、搭建nginx+ffmpeg 拉流,直接解压就可以使用,相关window10和linux如何搭建流服务器,请自行百度,不详叙述
链接:https://pan.baidu.com/s/1j-WL-EGNnI3xl_JWYexCWg
提取码:tzxm
复制这段内容后打开百度网盘手机App,操作更方便哦
启动window10+nginx+ffmpeg服务功能
Microsoft Windows [版本 10.0.19042.985]
(c) Microsoft Corporation。保留所有权利。
C:\Users\sxj>d:
D:\>cd nginx-1.7.11.3
D:\nginx-1.7.11.3>nginx.exe -c conf\nginx-win-rtmp.conf
拉流可以使用ffplay或者smplayer播放器~
参考手册学习一下基础知识:ffmpeg tutorial
学习的主线 1、先学习如何读取视频文件/啦网络摄像头流/读取摄像头--->h264--->yuv/bgr-->opencv(RGB)
2、在学习如何读取视频文件--->h264--->yuv-->推流
3、创建visual studio 2019 的项目工程
解压ffmpeg编译好的代码包,目录结构如下,vs2019引入头文件进行测试
同时将ffmpeg的dll文件拷贝到生成可执行文件目录下
下面代码完成了三件事:
(4)直接解析本地的视频,进行转opencv
(5)直接拉远程摄像头的url,进行转opencv
(6)使用opencv拉摄像头,进行推流
(7)使用ffmpeg拉摄像头,进行推流
4、然后直接上雷大神的代码吧,然后在他的基础上,进行改造和学习
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
};
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx;
int i, videoindex;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVFrame* pFrame, * pFrameYUV;
uint8_t* out_buffer;
AVPacket* packet;
int y_size;
int ret, got_picture;
struct SwsContext* img_convert_ctx;
//输入文件路径
char filepath[] = "F:\\sxj\\20210430\\testffmpeg\\x64\\Release\\1.mp4";
int frame_cnt;
av_register_all();//注册所有组件
avformat_network_init();
pFormatCtx = avformat_alloc_context();//其中负责申请一个AVFormatContext结构的内存, 并进行简单初始化
//打开输入视频文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
//获取视频文件信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;//找到视频的数组位置
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
//-------------获取视频上下文的结构信息-------
//-------------获取解码器的上下文结构信息------
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);//查找解码器
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {//打开解码器
printf("Could not open codec.\n");
return -1;
}
/*
* 在此处添加输出视频信息的代码
* 取自于pFormatCtx,使用fprintf()
*/
printf("视频的时长:%dμs\n", pFormatCtx->duration);//输入视频的时长(以微秒为单位)
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
int number = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
out_buffer = (uint8_t*)av_malloc(number * sizeof(uint8_t));
avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据
printf("--------------- File Information ----------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, 4, NULL, NULL, NULL);
/*
参数1:被转换源的宽
参数2:被转换源的高
参数3:被转换源的格式,eg:YUV、RGB……(枚举格式,也可以直接用枚举的代号表示eg:AV_PIX_FMT_YUV420P这些枚举的格式在libavutil/pixfmt.h中列出)
参数4:转换后指定的宽
参数5:转换后指定的高
参数6:转换后指定的格式同参数3的格式
参数7:转换所使用的算法,
参数8:NULL
参数9:NULL
参数10:NULL
*/
FILE* fpyuv = fopen("a.yuv", "wb+");
frame_cnt = 0;
//从输入文件读取一帧压缩数据
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == videoindex) {
/*
* 在此处添加输出H264码流的代码
* 取自于packet,使用fwrite()
*/
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解码一帧压缩数据
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
if (got_picture) {
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
printf("Decoded frame index: %d\n", frame_cnt);
/*
第一个参数即是由 sws_getContext 所取得的参数。
第二个 src 及第六个 dst 分別指向input 和 output 的 buffer。
第三个 srcStride 及第七 个 dstStride 分別指向 input 及 output 的 stride;如果不知道什么是 stride,姑且可以先把它看成是每一列的 byte 数。
第四个srcSliceY,就注解的意思来看,是指第一列要处理的位置;这里我是从头处理,所以直接填0。想知道更详细说明的人,可以参考 swscale.h 的注解。
第五个srcSliceH指的是 source slice 的高度
*/
fwrite(pFrameYUV->data[0], 1, pCodecCtx->width * pCodecCtx->height, fpyuv);
fwrite(pFrameYUV->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, fpyuv);
fwrite(pFrameYUV->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, fpyuv);
int height = pCodecCtx->height;
int width = pCodecCtx->width;
int size= pCodecCtx->width * pCodecCtx->height;
Mat img = Mat::zeros(height * 3 / 2, width, CV_8UC1);
memcpy(img.data, pFrameYUV->data[0], size);
memcpy(img.data + width * height, pFrameYUV->data[1], size / 4);
memcpy(img.data + width * height * 5 / 4, pFrameYUV->data[2], size / 4);
cv::cvtColor(img, img, cv::COLOR_YUV2BGR_I420);
imshow("demo", img);
waitKey(1);
frame_cnt++;
}
}
av_free_packet(packet);
}
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);//关闭解码器
avformat_close_input(&pFormatCtx);//关闭输入视频文件。
return 0;
}
新版本4.4.0代码
#include <stdio.h>
#include <string>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
#include "libavutil/imgutils.h"
};
using namespace std;
int main(int argc, char *argv[]) {
const char *path = "rtsp://admin:botech_123@192.168.10.219:554/h264/ch33/main/av_stream";
// AVDictionary *optional=NULL;
// av_dict_set(&optional,"stimeout","6000000",0);
// av_dict_set(&optional,"rstp_transport","tcp",0);
// av_dict_set(&optional,"buffer_size","1024000",0);
// av_dict_set(&optional,"max_delay","500000",0);
AVFormatContext *pFormat = NULL;
int ret = avformat_open_input(&pFormat, path, NULL, NULL);
if (ret < 0) {
perror("avformat_open_input");
avformat_free_context(pFormat);
return -1;
}
// av_dict_free(&optional);
printf("avformat_open_input successfully\n");
ret = avformat_find_stream_info(pFormat, NULL);
if (ret < 0) {
perror("avformat_find_stream_info\n");
return -1;
}
printf("avformat_find_stream_info successfully\n");
int time = pFormat->duration;
int mbittime = (time / 100000) / 60;
int mminttime = (time / 100000) % 60;
printf("video time: %d'm %d's\n", mbittime, mminttime);
av_dump_format(pFormat, 0, path, 0);
int videoindex = -1;
for (int i = 0; i < pFormat->nb_streams; i++) {
if (pFormat->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("don't find video stream\n");
return -1;
}
AVCodecParameters *codecParameters = pFormat->streams[videoindex]->codecpar;
printf("video width %d\n", codecParameters->width);
printf("video height %d\n", codecParameters->height);
AVCodec *pCodec = avcodec_find_decoder(codecParameters->codec_id);
AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
ret = avcodec_open2(pCodecCtx, pCodec, NULL);
if (ret < 0) {//打开解码器
printf("Could not open codec.\n");
return -1;
}
AVFrame *picture = av_frame_alloc();
picture->width = codecParameters->width;
picture->height = codecParameters->height;
picture->format = AV_PIX_FMT_YUV420P;
ret = av_frame_get_buffer(picture, 1);
if (ret < 0) {
printf("av_frame_get_buffer error\n");
return -1;
}
printf("picture->linesize[0] %d\n", picture->linesize[0]);
AVFrame *pFrame = av_frame_alloc();
pFrame->width = codecParameters->width;
pFrame->height = codecParameters->height;
pFrame->format = AV_PIX_FMT_YUV420P;
ret = av_frame_get_buffer(pFrame, 1);
if (ret < 0) {
printf("av_frame_get_buffer error\n");
return -1;
}
AVFrame *pFrameRGB = av_frame_alloc();
pFrameRGB->width = codecParameters->width;
pFrameRGB->height = codecParameters->height;
pFrameRGB->format = AV_PIX_FMT_RGB24;
ret = av_frame_get_buffer(pFrameRGB, 1);
if (ret < 0) {
printf("av_frame_get_buffer error\n");
return -1;
}
int picture_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codecParameters->width, codecParameters->height,
1);//计算这个格式的图片,需要多少字节来存储
uint8_t *out_buff = (uint8_t *) av_malloc(picture_size * sizeof(uint8_t));
av_image_fill_arrays(picture->data, picture->linesize, out_buff, AV_PIX_FMT_YUV420P, codecParameters->width,
codecParameters->height, 1);
//这个函数 是缓存转换格式,可以不用 以为上面已经设置了AV_PIX_FMT_YUV420P
SwsContext *img_convert_ctx = sws_getContext(codecParameters->width, codecParameters->height, AV_PIX_FMT_YUV420P,
codecParameters->width, codecParameters->height, AV_PIX_FMT_RGB24, 4,
NULL, NULL, NULL);
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
while (av_read_frame(pFormat, packet) >= 0) {
if (packet->stream_index == videoindex) {
ret = avcodec_send_packet(pCodecCtx, packet);
if (ret < 0) {
printf("avcodec_send_packet error\n");
continue;
}
av_packet_unref(packet);
int got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
if (got_picture < 0) {
printf("avcodec_receive_frame error\n");
continue;
}
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0,
codecParameters->height,
pFrameRGB->data, pFrameRGB->linesize);
cv::Mat mRGB(cv::Size(codecParameters->width, codecParameters->height), CV_8UC3);
mRGB.data = (unsigned char *) pFrameRGB->data[0];
cv::Mat mBGR;
cv::cvtColor(mRGB, mBGR, cv::COLOR_RGB2BGR);
cv::imshow("demo", mBGR);
cv::waitKey(1);
}
}
av_frame_free(&picture);
av_frame_free(&pFrame);
av_frame_free(&pFrameRGB);
avformat_free_context(pFormat);
return 0;
}
使用播放器YUV Player download | SourceForge.net 打开生成的解码yuv视频即可,
5、将yuv数据转成bgr数据并进行显示,仅供练习使用
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
};
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx;
int i, videoindex;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;
AVFrame* pFrame, * pFrameRGB;
uint8_t* out_buffer;
AVPacket* packet;
int y_size;
int ret, got_picture;
struct SwsContext* img_convert_ctx;
//输入文件路径
char filepath[] = "F:\\sxj\\20210430\\testffmpeg\\x64\\Release\\1.mp4";
// 若果拉取视频流的话 char filepath[] ="rtsp://ubuntu:ubuntu@192.168.**.**:554/user=admin&password=&channel=1&stream=0.sdp?real_stream";
int frame_cnt;
av_register_all();//注册所有组件
avformat_network_init();
pFormatCtx = avformat_alloc_context();//其中负责申请一个AVFormatContext结构的内存, 并进行简单初始化
//打开输入视频文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
//获取视频文件信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;//找到视频的数组位置
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
//-------------获取视频上下文的结构信息-------
//-------------获取解码器的上下文结构信息------
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);//查找解码器
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {//打开解码器
printf("Could not open codec.\n");
return -1;
}
/*
* 在此处添加输出视频信息的代码
* 取自于pFormatCtx,使用fprintf()
*/
printf("视频的时长:%dμs\n", pFormatCtx->duration);//输入视频的时长(以微秒为单位)
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
int number = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
out_buffer = (uint8_t*)av_malloc(number * sizeof(uint8_t));
avpicture_fill((AVPicture*)pFrameRGB, out_buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据
printf("--------------- File Information ----------------\n");
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, 4, NULL, NULL, NULL);
/*
参数1:被转换源的宽
参数2:被转换源的高
参数3:被转换源的格式,eg:YUV、RGB……(枚举格式,也可以直接用枚举的代号表示eg:AV_PIX_FMT_YUV420P这些枚举的格式在libavutil/pixfmt.h中列出)
参数4:转换后指定的宽
参数5:转换后指定的高
参数6:转换后指定的格式同参数3的格式
参数7:转换所使用的算法,
参数8:NULL
参数9:NULL
参数10:NULL
*/
FILE* fprgb = fopen("a", "wb+");//后缀名无所谓,格式是rgb24 yuv 就显示 什么样的视频
frame_cnt = 0;
//从输入文件读取一帧压缩数据
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == videoindex) {
/*
* 在此处添加输出H264码流的代码
* 取自于packet,使用fwrite()
*/
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解码一帧压缩数据
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
if (got_picture) {
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
printf("Decoded frame index: %d\n", frame_cnt);
/*
第一个参数即是由 sws_getContext 所取得的参数。
第二个 src 及第六个 dst 分別指向input 和 output 的 buffer。
第三个 srcStride 及第七 个 dstStride 分別指向 input 及 output 的 stride;如果不知道什么是 stride,姑且可以先把它看成是每一列的 byte 数。
第四个srcSliceY,就注解的意思来看,是指第一列要处理的位置;这里我是从头处理,所以直接填0。想知道更详细说明的人,可以参考 swscale.h 的注解。
第五个srcSliceH指的是 source slice 的高度
*/
fwrite(pFrameRGB->data[0], 1, pCodecCtx->width * pCodecCtx->height*3, fprgb);
Mat mRGB( Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data = (uchar*)pFrameRGB->data[0];
Mat mBGR;
cv::cvtColor(mRGB, mBGR, cv::COLOR_RGB2BGR);
imshow("demo", mBGR);
waitKey(1);
frame_cnt++;
}
}
av_free_packet(packet);
}
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameRGB);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);//关闭解码器
avformat_close_input(&pFormatCtx);//关闭输入视频文件。
return 0;
}
6、进行摄像头拉流显示和存储,参考 "cpp手艺人大佬”代码,自己做了修改,主要在内存释放问题上,否则存在问题
#include <iostream>
#include <vector>
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <opencv2/opencv.hpp>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
using namespace std;
using namespace cv;
int main() {
const char* out_url = "rtmp://192.168.234.133:1935/live/livestream";
// 注册所有网络协议
avformat_network_init();
// 输出的数据结构
AVFrame* yuv = NULL;
Mat frame;
// 1.使用opencv 打开usb 摄像头
VideoCapture video_ptr;
video_ptr.open(0);
if (!video_ptr.isOpened()) {
cout << "camera open usb camera error" << endl;
return -1;
}
cout << "open usb camera successful." << endl;
int width = video_ptr.get(CAP_PROP_FRAME_WIDTH);
int height = video_ptr.get(CAP_PROP_FRAME_HEIGHT);
int fps = video_ptr.get(CAP_PROP_FPS);
// 如果fps为0,这里就设置25。因为在fps=0时,调用avcodec_open2返回-22,
// 参数不合法
if (0 == fps) { fps = 25; }
// 2.初始化格式转换上下文
SwsContext* sws_context = NULL;
sws_context = sws_getCachedContext(sws_context,
width, height, AV_PIX_FMT_BGR24, // 源格式
width, height, AV_PIX_FMT_YUV420P, // 目标格式
SWS_BICUBIC, // 尺寸变化使用算法
0, 0, 0);
if (NULL == sws_context) {
cout << "sws_getCachedContext error" << endl;
return -1;
}
// 3.初始化输出的数据结构
yuv = av_frame_alloc();
yuv->format = AV_PIX_FMT_YUV420P;
yuv->width = width;
yuv->height = height;
yuv->pts = 0;
// 分配yuv空间
int ret_code = av_frame_get_buffer(yuv, 32);
if (0 != ret_code) {
cout << " yuv init fail" << endl;
return -1;
}
// 4.初始化编码上下文
// 4.1找到编码器
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (NULL == codec) {
cout << "Can't find h264 encoder." << endl;
return -1;
}
// 4.2创建编码器上下文
AVCodecContext* codec_context = avcodec_alloc_context3(codec);
if (NULL == codec_context) {
cout << "avcodec_alloc_context3 failed." << endl;
return -1;
}
// 4.3配置编码器参数
// vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
codec_context->codec_id = codec->id;
codec_context->thread_count = 8;
// 压缩后每秒视频的bit流 50k
codec_context->bit_rate = 50 * 1024 * 8;
codec_context->width = width;
codec_context->height = height;
codec_context->time_base = { 1, fps };
codec_context->framerate = { fps, 1 };
// 画面组的大小,多少帧一个关键帧
codec_context->gop_size = 50;
codec_context->max_b_frames = 0;
codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
codec_context->qmin = 10;
codec_context->qmax = 51;
AVDictionary* codec_options = nullptr;
//(baseline | high | high10 | high422 | high444 | main)
av_dict_set(&codec_options, "profile", "baseline", 0);
av_dict_set(&codec_options, "preset", "superfast", 0);
av_dict_set(&codec_options, "tune", "zerolatency", 0);
// 4.4打开编码器上下文
ret_code = avcodec_open2(codec_context, codec, &codec_options);
if (0 != ret_code) {
return -1;
}
cout << "avcodec_open2 success!" << endl;
// 5.输出封装器和视频流配置
// 5.1创建输出封装器上下文
// rtmp flv封装器
AVFormatContext* format_context = nullptr;
ret_code = avformat_alloc_output_context2(&format_context, 0, "flv", out_url);
if (0 != ret_code) {
return -1;
}
// 5.2添加视频流
AVStream* vs = avformat_new_stream(format_context, NULL);
if (NULL == vs) {
cout << "avformat_new_stream failed." << endl;
return -1;
}
vs->codecpar->codec_tag = 0;
// 从编码器复制参数
avcodec_parameters_from_context(vs->codecpar, codec_context);
av_dump_format(format_context, 0, out_url, 1);
// 打开rtmp 的网络输出IO
ret_code = avio_open(&format_context->pb, out_url, AVIO_FLAG_WRITE);
if (0 != ret_code) {
cout << "avio_open failed." << endl;
return -1;
}
// 写入封装头
ret_code = avformat_write_header(format_context, NULL);
if (0 != ret_code) {
cout << "avformat_write_header failed." << endl;
return -1;
}
AVPacket pack;
memset(&pack, 0, sizeof(pack));
int vpts = 0;
uint8_t* in_data[AV_NUM_DATA_POINTERS] = { 0 };
int in_size[AV_NUM_DATA_POINTERS] = { 0 };
for (;;) {
// 读取rtsp视频帧,解码视频帧
video_ptr >> frame;
// If the frame is empty, break immediately
if (frame.empty()) break;
imshow("video", frame);
waitKey(1);
// rgb to yuv
in_data[0] = frame.data;
// 一行(宽)数据的字节数
in_size[0] = frame.cols * frame.elemSize();
int h = sws_scale(sws_context, in_data, in_size, 0, frame.rows,
yuv->data, yuv->linesize);
if (h <= 0) { continue; }
// h264编码
yuv->pts = vpts;
vpts++;
ret_code = avcodec_send_frame(codec_context, yuv);
if (0 != ret_code) { continue; }
ret_code = avcodec_receive_packet(codec_context, &pack);
if (0 != ret_code || pack.buf !=nullptr) {//
cout << "avcodec_receive_packet." << endl;
}
else {
cout << "avcodec_receive_packet contiune." << endl;
continue;
}
// 推流
pack.pts = av_rescale_q(pack.pts, codec_context->time_base, vs->time_base);
pack.dts = av_rescale_q(pack.dts, codec_context->time_base, vs->time_base);
pack.duration = av_rescale_q(pack.duration,
codec_context->time_base,
vs->time_base);
ret_code = av_interleaved_write_frame(format_context, &pack);
if (0 != ret_code)
{
cout << "pack is error" << endl;
}
av_packet_unref(&pack);
frame.release();
}
av_dict_free(&codec_options);
avcodec_free_context(&codec_context);
av_frame_free(&yuv);
avio_close(format_context->pb);
avformat_free_context(format_context);
sws_freeContext(sws_context);
video_ptr.release();
destroyAllWindows();
return 0;
}
7、使用ffmpeg拉摄像头,进行推流
/**
* 最简单的基于FFmpeg的推流器(推送RTMP)
* Simplest FFmpeg Streamer (Send RTMP)
*
* 雷霄骅 Lei Xiaohua 神级人物 怀念你 。。。。
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
*/
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif
#endif
int main(int argc, char* argv[])
{
AVOutputFormat *ofmt = NULL;
//输入对应一个AVFormatContext,输出对应一个AVFormatContext
//(Input AVFormatContext and Output AVFormatContext)
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
//* AVFormatContext是一个统筹全局的结构, 包含一些视频文件名,视频时长,视频码率等封装格式信息。
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int videoindex=-1;
int frame_index=0;
int64_t start_time=0;
//in_filename = "cuc_ieschool.mov";
//in_filename = "cuc_ieschool.mkv";
//in_filename = "cuc_ieschool.ts";
//in_filename = "cuc_ieschool.mp4";
//in_filename = "cuc_ieschool.h264";
in_filename = "./1.mp4";//输入URL(Input file URL)
//in_filename = "shanghai03_p.h264";
out_filename = "rtmp://ubuntu:ubuntu@192.168.2.101/live/livestream";//输出 URL(Output URL)[RTMP]
//out_filename = "rtp://233.233.233.233:6666";//输出 URL(Output URL)[UDP]
av_register_all();
//Network 这里注册了所有的文件格式和编解码器的库
avformat_network_init();
//输入(Input)
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf( "Could not open input file.");
goto end;
}
/*
ps:函数调用成功之后处理过的AVFormatContext结构体
file:打开的视音频流的URL
fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat
dictionay:附加的一些选项,一般情况下可以设置为NULL
*/
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf( "Failed to retrieve input stream information");
goto end;
}
/*
ic:输入的AVFormatContext。
options:额外的选项,目前没有深入研究过。
*/
for(i=0; i<ifmt_ctx->nb_streams; i++)
if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
/*
仅仅处理视频流,而不是音频流。查找视频流的id。
*/
av_dump_format(ifmt_ctx, 0, in_filename, 0);
/*
打印关于输入或输出格式的详细信息
*/
//输出(Output)
avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
/*
ctx:函数调用成功之后创建的AVFormatContext结构体。
oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。
PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。
函数执行成功的话,其返回值大于等于0。 */
//avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDP
if (!ofmt_ctx) {
printf( "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
//根据输入流创建输出流(Create output AVStream according to input AVStream)
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
printf( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
//复制AVCodecContext的设置(Copy the settings of AVCodecContext)
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
printf( "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
//Dump Format------------------
av_dump_format(ofmt_ctx, 0, out_filename, 1);
//打开输出URL(Open output URL)
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
printf( "Could not open output URL '%s'", out_filename);
goto end;
}
}
//写文件头(Write file header)
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
printf( "Error occurred when opening output URL\n");
goto end;
}
start_time=av_gettime();
while (1) {
AVStream *in_stream, *out_stream;
//获取一个AVPacket(Get an AVPacket)
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}
//Important:Delay
if(pkt.stream_index==videoindex){
AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q={1,AV_TIME_BASE};
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time);
}
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
/* copy packet */
//转换PTS/DTS(Convert PTS/DTS)
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
//Print to Screen
if(pkt.stream_index==videoindex){
printf("Send %8d video frames to output URL\n",frame_index);
frame_index++;
}
//ret = av_write_frame(ofmt_ctx, &pkt);
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
printf( "Error muxing packet\n");
break;
}
av_free_packet(&pkt);
}
//写文件尾(Write file trailer)
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
printf( "Error occurred.\n");
return -1;
}
return 0;
}