ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

FFMPEG--获取usb摄像头视频

2020-11-24 15:58:09  阅读:236  来源: 互联网

标签:std FFMPEG CGUsbCamera -- ret int av include 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
)

标签:std,FFMPEG,CGUsbCamera,--,ret,int,av,include,usb
来源: https://blog.csdn.net/xxboy61/article/details/110083489

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有