FFMPEG--获取usb摄像头视频

环境

FFMPEG--获取usb摄像头视频

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
)
上一篇:QT搭建Ffmpeg开发环境gcc版本


下一篇:C语言编程100题-4.3