环境
FFMPEG 版本
ffmpeg-4.3.1.tar.bz2
./configure --prefix=./install --enable-shared --disable-static --disable-x86asm
文件目录
├── 3rdparty
│ ├── ffmpeg
│ │ ├── include
│ │ │ ├── libavcodec
│ │ │ ├── libavdevice
│ │ │ ├── libavfilter
│ │ │ ├── libavformat
│ │ │ ├── libavutil
│ │ │ ├── libswresample
│ │ │ └── libswscale
│ │ └── lib
│ └── opencv
│ └── README.md
├── CMakeLists.txt
├── build
├── docs
├── incs
│ ├── CGUsbCamera.hpp
│ └── common.hpp
├── main.cpp
├── srcs
│ └── CGUsbCamera.cpp
common.hpp
#pragma once
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/fifo.h"
#include "libavutil/imgutils.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/samplefmt.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
}
#define RET_OK (0)
#define RET_FAILED (-1)
CGUsbCamera.hpp
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include "common.hpp"
class SwsScaleContext
{
public:
SwsScaleContext() {}
void SetSrcResolution(int width, int height)
{
srcWidth = width;
srcHeight = height;
}
void SetDstResolution(int width, int height)
{
dstWidth = width;
dstHeight = height;
}
void SetFormat(AVPixelFormat iformat, AVPixelFormat oformat)
{
this->iformat = iformat;
this->oformat = oformat;
}
public:
int srcWidth;
int srcHeight;
int dstWidth;
int dstHeight;
AVPixelFormat iformat;
AVPixelFormat oformat;
};
class CGUsbCamera
{
public:
AVFormatContext *inputContext = nullptr;
AVCodecContext *encodeContext = nullptr;
AVFormatContext *outputContext = nullptr;
AVFrame *videoFrame = nullptr;
AVFrame *pSwsVideoFrame = nullptr;
uint8_t *pSwpBuffer = nullptr;
struct SwsContext *pSwsContext = nullptr;
SwsScaleContext swsScaleContext;
int64_t startTime;
int64_t lastReadPacktTime;
int64_t packetCount = 0;
std::thread m_hThread;
private:
bool m_bStoped{true};
bool m_bInputInited{false};
bool m_bOutputInited{false};
std::shared_ptr<std::mutex> ctxLock = std::make_shared<std::mutex>();
public:
CGUsbCamera();
~CGUsbCamera();
void StartDecode();
void StopDecode();
private:
void Init();
void ReadingThrd(void *pParam);
void run();
void DecodeAndEncode();
int InitDecodeContext(AVStream *inputStream);
int initEncoderCodec(AVStream *inputStream, AVCodecContext **encodeContext);
int initSwsContext(struct SwsContext **pSwsContext, SwsScaleContext *swsScaleContext);
int initSwsFrame(AVFrame *pSwsFrame, int iWidth, int iHeight);
int OpenInput(std::string inputUrl);
void CloseInput();
int OpenOutput(std::string outUrl, AVCodecContext *encodeCodec);
void CloseOutput();
bool Decode(AVStream *inputStream, AVPacket *packet, AVFrame *frame);
int WritePacket(std::shared_ptr<AVPacket> packet);
std::shared_ptr<AVPacket> ReadPacketFromSource();
std::shared_ptr<AVPacket> Encode(AVCodecContext *encodeContext, AVFrame *frame);
};
CGUsbCamera.cpp
#include "CGUsbCamera.hpp"
#include <exception>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
using namespace std;
static string AvErrToStr(int avErrCode)
{
const auto bufSize = 1024U;
char *errString = (char *) calloc(bufSize, sizeof(*errString));
if (!errString)
{
return string();
}
if (0 != av_strerror(avErrCode, errString, bufSize - 1))
{
free(errString);
stringstream ss;
ss << "Unknown error with code " << avErrCode;
return ss.str();
}
string str(errString);
free(errString);
return str;
}
CGUsbCamera::CGUsbCamera()
{
this->Init();
}
CGUsbCamera::~CGUsbCamera()
{
av_frame_free(&this->videoFrame);
av_frame_free(&this->pSwsVideoFrame);
avcodec_close(this->encodeContext);
this->CloseInput();
this->CloseOutput();
}
/* 打开设备获取设备基本信息 */
int CGUsbCamera::OpenInput(std::string inputUrl)
{
inputContext = avformat_alloc_context();
lastReadPacktTime = av_gettime();
AVDictionary *format_opts = nullptr;
const char *input_format_name = "avfoundation";
const char *url = "0";
av_dict_set(&format_opts, "video_size", "1280x720", 0);
av_dict_set(&format_opts, "framerate", "30", 0);
av_dict_set(&format_opts, "pixel_format", "nv12", 0);
AVInputFormat *ifmt = av_find_input_format(input_format_name);
/* 打开一个文件并解析。可解析内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引。 */
int ret = avformat_open_input(&inputContext, url, ifmt, &format_opts);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avformat_open_input failed with :%s\n", AvErrToStr(ret).c_str());
return ret;
}
/* 探测获取封装格式的上下文信息 */
ret = avformat_find_stream_info(inputContext, nullptr);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avformat_find_stream_info failed with :%s\n", AvErrToStr(ret).c_str());
return ret;
}
ret = InitDecodeContext(inputContext->streams[0]);
if (RET_OK != ret)
{
return ret;
}
ret = initEncoderCodec(inputContext->streams[0], &encodeContext);
if (RET_OK != ret)
{
return ret;
}
m_bInputInited = true;
return RET_OK;
}
/* 封装写文件
编码后的视频数据不能直接写入文件,需要经过封装(打包)成mp4格式。首先我们得创建输出上下文,并为它指定视频格式。
*/
int CGUsbCamera::OpenOutput(std::string outUrl, AVCodecContext *encodeCodec)
{
int ret = RET_OK;
/* 初始化输出的AVFormatContext结构体 */
ret = avformat_alloc_output_context2(&outputContext, nullptr, "mp4", outUrl.c_str());
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avformat_alloc_output_context2 failed with :%s\n", AvErrToStr(ret).c_str());
goto lblError;
}
/* 以只写方式打开输出文件 */
ret = avio_open2(&outputContext->pb, outUrl.c_str(), AVIO_FLAG_WRITE, nullptr, nullptr);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avio_open2 failed with :%s\n", AvErrToStr(ret).c_str());
goto lblError;
}
for (unsigned int i = 0; i < inputContext->nb_streams; i++)
{
/* 创建一个输出流通道 */
AVStream *stream = avformat_new_stream(outputContext, encodeCodec->codec);
ret = avcodec_copy_context(stream->codec, encodeCodec);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avcodec_copy_context failed with :%s\n", AvErrToStr(ret).c_str());
goto lblError;
}
}
ret = avformat_write_header(outputContext, nullptr);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avformat_write_header failed with :%s\n", AvErrToStr(ret).c_str());
goto lblError;
}
av_log(NULL, AV_LOG_FATAL, "Open output file [%s] success\n", outUrl.c_str());
this->swsScaleContext.SetSrcResolution(this->inputContext->streams[0]->codec->width,
this->inputContext->streams[0]->codec->height);
this->swsScaleContext.SetDstResolution(this->encodeContext->width, this->encodeContext->height);
this->swsScaleContext.SetFormat(this->inputContext->streams[0]->codec->pix_fmt, this->encodeContext->pix_fmt);
this->initSwsContext(&this->pSwsContext, &this->swsScaleContext);
this->initSwsFrame(this->pSwsVideoFrame, this->encodeContext->width, this->encodeContext->height);
return ret;
lblError:
if (outputContext)
{
for (unsigned int i = 0; i < outputContext->nb_streams; i++)
{
avcodec_close(outputContext->streams[i]->codec);
}
avformat_close_input(&outputContext);
}
return ret;
}
/* 读取视频包 */
std::shared_ptr<AVPacket> CGUsbCamera::ReadPacketFromSource()
{
std::shared_ptr<AVPacket> packet(static_cast<AVPacket *>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) {
av_packet_free(&p);
av_freep(&p);
});
av_init_packet(packet.get());
lastReadPacktTime = av_gettime();
/* 读取具体的音/视频帧数据 */
int ret = av_read_frame(inputContext, packet.get());
if (RET_OK == ret)
{
return packet;
}
else
{
av_log(NULL, AV_LOG_ERROR, "av_read_frame failed with :%s\n", AvErrToStr(ret).c_str());
return nullptr;
}
}
void CGUsbCamera::Init()
{
av_register_all();
avfilter_register_all();
avformat_network_init();
avdevice_register_all();
av_log_set_level(AV_LOG_ERROR);
this->videoFrame = av_frame_alloc();
this->pSwsVideoFrame = av_frame_alloc();
this->startTime = av_gettime();
this->m_bStoped = true;
m_hThread = std::thread(&CGUsbCamera::ReadingThrd, this, (void *) this);
}
/* 封装线程函数 */
void CGUsbCamera::ReadingThrd(void *pParam)
{
CGUsbCamera *pTask = (CGUsbCamera *) pParam;
pTask->run();
}
void CGUsbCamera::CloseInput()
{
try
{
if (inputContext)
{
avformat_close_input(&inputContext);
}
if (pSwsContext)
{
sws_freeContext(pSwsContext);
}
m_bInputInited = false;
}
catch (exception &e)
{
std::cerr << "CloseInput failed with " << e.what() << std::endl;
}
}
void CGUsbCamera::CloseOutput()
{
try
{
if (outputContext)
{
av_write_trailer(outputContext);
avformat_close_input(&outputContext);
m_bOutputInited = false;
}
}
catch (exception &e)
{
std::cerr << "CloseInput failed with " << e.what() << std::endl;
}
}
int CGUsbCamera::WritePacket(shared_ptr<AVPacket> packet)
{
auto inputStream = inputContext->streams[packet->stream_index];
auto outputStream = outputContext->streams[packet->stream_index];
packet->pts =
packetCount * (outputContext->streams[0]->time_base.den) / outputContext->streams[0]->time_base.num / 30;
packet->dts =
packetCount * (outputContext->streams[0]->time_base.den) / outputContext->streams[0]->time_base.num / 30;
packetCount++;
return av_interleaved_write_frame(outputContext, packet.get());
}
/* 初始化解码视频包,创建解码器-->初始化解码器-->调用解码Ffmpeg API进行解码 */
int CGUsbCamera::InitDecodeContext(AVStream *inputStream)
{
auto codecId = inputStream->codec->codec_id;
auto codec = avcodec_find_decoder(codecId);
if (!codec)
{
av_log(NULL, AV_LOG_ERROR, "avcodec_find_decoder failed\n");
return RET_FAILED;
}
int ret = avcodec_open2(inputStream->codec, codec, NULL);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avcodec_open2 failed with :%s\n", AvErrToStr(ret).c_str());
}
return ret;
}
/* 编码 将视频包解码后要再编码的,编码之前同样需要初始化编码器 */
int CGUsbCamera::initEncoderCodec(AVStream *inputStream, AVCodecContext **encodeContext)
{
auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
av_log(NULL, AV_LOG_ERROR, "avcodec_find_decoder failed\n");
return RET_FAILED;
}
(*encodeContext) = avcodec_alloc_context3(codec);
(*encodeContext)->codec_id = codec->id;
(*encodeContext)->has_b_frames = 0;
(*encodeContext)->time_base.num = inputStream->codec->time_base.num;
(*encodeContext)->time_base.den = inputStream->codec->time_base.den;
(*encodeContext)->pix_fmt = *codec->pix_fmts;
(*encodeContext)->width = inputStream->codec->width;
(*encodeContext)->height = inputStream->codec->height;
(*encodeContext)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
int ret = avcodec_open2((*encodeContext), codec, nullptr);
if (RET_OK != ret)
{
av_log(NULL, AV_LOG_ERROR, "avcodec_open2 failed with :%s\n", AvErrToStr(ret).c_str());
}
return ret;
}
/* 解码 */
bool CGUsbCamera::Decode(AVStream *inputStream, AVPacket *packet, AVFrame *frame)
{
int gotFrame = 0;
auto ret = avcodec_decode_video2(inputStream->codec, frame, &gotFrame, packet);
if (ret >= 0 && gotFrame != 0)
{
return true;
}
return false;
}
/* 编码 */
std::shared_ptr<AVPacket> CGUsbCamera::Encode(AVCodecContext *encodeContext, AVFrame *frame)
{
int gotPacket = 0;
std::shared_ptr<AVPacket> pkt(static_cast<AVPacket *>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) {
av_packet_free(&p);
av_freep(&p);
});
av_init_packet(pkt.get());
pkt->data = NULL;
pkt->size = 0;
int ret = avcodec_encode_video2(encodeContext, pkt.get(), frame, &gotPacket);
if (ret >= 0 && gotPacket)
{
return pkt;
}
else
{
return nullptr;
}
}
int CGUsbCamera::initSwsContext(struct SwsContext **pSwsContext, SwsScaleContext *swsScaleContext)
{
*pSwsContext =
sws_getContext(swsScaleContext->srcWidth, /* 输入图像的宽度 */
swsScaleContext->srcHeight, /* 输入图像的宽度 */
swsScaleContext->iformat, /* 输入图像的像素格式 */
swsScaleContext->dstWidth, /* 输出图像的宽度 */
swsScaleContext->dstHeight, /* 输出图像的高度 */
swsScaleContext->oformat, /* 输出图像的像素格式 */
SWS_BICUBIC, /* 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR */
nullptr, /* 输入图像的滤波器信息, 若不需要传NULL */
nullptr, /* 输出图像的滤波器信息, 若不需要传NULL */
nullptr /* 特定缩放算法需要的参数(?),默认为NULL */);
if (!pSwsContext)
{
return RET_FAILED;
}
return RET_OK;
}
int CGUsbCamera::initSwsFrame(AVFrame *pSwsFrame, int iWidth, int iHeight)
{
/* 通过指定像素格式、图像宽、图像高来计算所需的内存大小 */
int numBytes = av_image_get_buffer_size(encodeContext->pix_fmt, iWidth, iHeight, 1);
pSwpBuffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(pSwsFrame->data, pSwsFrame->linesize, pSwpBuffer, encodeContext->pix_fmt, iWidth, iHeight, 1);
pSwsFrame->width = iWidth;
pSwsFrame->height = iHeight;
pSwsFrame->format = encodeContext->pix_fmt;
return RET_OK;
}
void CGUsbCamera::StartDecode()
{
std::lock_guard<std::mutex> lock(*this->ctxLock);
m_bStoped = false;
}
void CGUsbCamera::StopDecode()
{
std::lock_guard<std::mutex> lock(*this->ctxLock);
m_bStoped = true;
}
void CGUsbCamera::DecodeAndEncode()
{
int ret = RET_OK;
while (1)
{
{
std::lock_guard<std::mutex> lock(*this->ctxLock);
if (m_bStoped)
{
std::this_thread::sleep_for(std::chrono::milliseconds(30));
break;
}
}
try
{
auto packet = ReadPacketFromSource();
if (packet && packet->stream_index == 0)
{
if (Decode(inputContext->streams[0], packet.get(), videoFrame))
{
/* 视频像素格式和分辨率的转换 */
sws_scale(pSwsContext, (const uint8_t *const *) videoFrame->data, videoFrame->linesize, 0,
inputContext->streams[0]->codec->height, (uint8_t *const *) pSwsVideoFrame->data,
pSwsVideoFrame->linesize);
auto packetEncode = Encode(encodeContext, pSwsVideoFrame);
if (packetEncode)
{
ret = WritePacket(packetEncode);
}
}
}
}
catch (exception &e)
{
std::cerr << "CGUsbCamera::run failed with " << e.what() << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
}
void CGUsbCamera::run()
{
int ret = RET_OK;
while (1)
{
if (m_bStoped)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
try
{
int ret = OpenInput("video=USB2.0 Camera");
if (RET_OK != ret)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
ret = OpenOutput("usbCamera.mp4", encodeContext);
if (RET_OK != ret)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
DecodeAndEncode();
CloseInput();
CloseOutput();
}
catch (exception &e)
{
CloseInput();
CloseOutput();
}
}
}
main.cpp
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include "CGUsbCamera.hpp"
using namespace std;
int main(int argc, char *argv[])
{
std::unique_ptr<CGUsbCamera> usbCamera = std::make_unique<CGUsbCamera>();
usbCamera->StartDecode();
while (1)
{
if (av_gettime() - usbCamera->startTime > 10 * 1000 * 1000)
{
std::cout << "time out ......" << std::endl;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
usbCamera->StopDecode();
usbCamera->m_hThread.join();
cout << "Get Picture End " << endl;
return 0;
}
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(demo)
set(FFMPEG_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/include CACHE INTERNAL "FFMPEG_INCLUDE_DIR")
set(FFMPEG_LIB_DIR ${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/lib CACHE INTERNAL "FFMPEG_LIB_DIR")
# set(OPENCV_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/3rdparty/opencv/include CACHE INTERNAL "OPENCV_INCLUDE_DIR")
# set(OPENCV_LIB_DIR ${PROJECT_SOURCE_DIR}/3rdparty/opencv/lib CACHE INTERNAL "OPENCV_LIB_DIR")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_BUILD_TYPE "Debug")
# set(CMAKE_BUILD_TYPE "Release")
if( CMAKE_BUILD_TYPE STREQUAL "Debug" )
add_definitions(-DDEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -w -pthread")
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "" FORCE)
elseif( CMAKE_BUILD_TYPE STREQUAL "Debug" )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -w -pthread -fopenmp")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
endif()
include_directories(${FFMPEG_INCLUDE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/incs)
link_directories (
${FFMPEG_LIB_DIR}
)
file(GLOB SRCS ${PROJECT_SOURCE_DIR}/srcs/*.cpp)
add_executable(
${PROJECT_NAME}
main.cpp
${SRCS}
)
target_link_libraries(
${PROJECT_NAME}
PUBLIC
${FFMPEG_LIB_DIR}/libavcodec.58.91.100.dylib
${FFMPEG_LIB_DIR}/libavdevice.58.10.100.dylib
${FFMPEG_LIB_DIR}/libavfilter.7.85.100.dylib
${FFMPEG_LIB_DIR}/libavformat.58.45.100.dylib
${FFMPEG_LIB_DIR}/libavutil.56.51.100.dylib
${FFMPEG_LIB_DIR}/libswresample.3.7.100.dylib
${FFMPEG_LIB_DIR}/libswscale.5.7.100.dylib
)