* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
* Description: VideoDecoder and videoEncoder tutorials.
* Author: MindX SDK
* Create: 2024
* History: NA
*/
#include <csignal>
#include <iostream>
#include <chrono>
#include <sys/time.h>
#include <thread>
#include <thread>
#include "acl/acl.h"
#include "acl/acl_rt.h"
#include "MxBase/Log/Log.h"
#include "MxBase/DeviceManager/DeviceManager.h"
#include "MxBase/E2eInfer/Image/Image.h"
#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h"
#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h"
#include "MxBase/MxBase.h"
#include "BlockingQueue.h"
extern "C" {
#include <libavformat/avformat.h>
}
using namespace std;
using namespace MxBase;
const int TIME_OUT = 3000;
const int QUEUE_SIZE = 1000;
const int FRAME_WAIT_TIME = 15;
const int DEFAULT_DEVICE_ID = 0;
const int DEFAULT_CHANNEL_ID = 0;
const std::string DEFAULT_SAVED_FILE_PATH = "./output";
static bool g_sendSignial = false;
static bool g_readVideoEnded = false;
static bool g_vdecEnded = false;
static bool g_vencEnded = false;
static void SigHandler(int signal)
{
if (signal == SIGINT) {
g_sendSignial = true;
}
}
struct DecodedFrame {
MxBase::Image image;
uint32_t frameId = 0;
uint32_t channelId = 0;
};
struct EncodedFrame {
std::shared_ptr<uint8_t> data;
uint32_t dataSize = 0;
uint32_t frameId = 0;
uint32_t channelId = 0;
};
BlockingQueue<EncodedFrame> g_pullerToVdecQueue(QUEUE_SIZE);
BlockingQueue<DecodedFrame> g_vdecToVencQueue(QUEUE_SIZE);
BlockingQueue<EncodedFrame> g_vencToFileSaveQueue(QUEUE_SIZE);
AVFormatContext* CreateFormatContext(const std::string &filePath)
{
AVFormatContext *formatContext = nullptr;
AVDictionary *options = nullptr;
int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, nullptr);
if (options != nullptr) {
av_dict_free(&options);
}
if (ret != 0) {
LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret;
return nullptr;
}
ret = avformat_find_stream_info(formatContext, nullptr);
if (ret != 0) {
LogError << "Couldn't find stream information";
return nullptr;
}
return formatContext;
}
void GetFrame(AVPacket& pkt, EncodedFrame& encodedFrame, AVFormatContext* pFormatCtx)
{
av_init_packet(&pkt);
int avRet = av_read_frame(pFormatCtx, &pkt);
if (avRet != 0) {
LogWarn << "[StreamPuller] Channel read frame failed, continue!";
if (avRet == AVERROR_EOF) {
g_readVideoEnded = true;
LogWarn << "[StreamPuller] Channel streamPuller is EOF, over!";
}
return;
} else {
if (pkt.size <= 0) {
LogError << "Invalid pkt.size: " << pkt.size;
return;
}
auto hostDeleter = [](void *dataPtr) -> void {aclrtFreeHost(dataPtr);};
MemoryData data(pkt.size, MemoryData::MEMORY_HOST);
MemoryData src((void *)(pkt.data), pkt.size, MemoryData::MEMORY_HOST_MALLOC);
APP_ERROR ret = MemoryHelper::MxbsMallocAndCopy(data, src);
if (ret != APP_ERR_OK) {
LogError << "MxbsMallocAndCopy failed!" << std::endl;
}
std::shared_ptr<uint8_t> imageData((uint8_t*)data.ptrData, hostDeleter);
encodedFrame.data = imageData;
encodedFrame.dataSize = pkt.size;
av_packet_unref(&pkt);
}
}
void StreamPullerThread(const std::string filePath, const uint32_t width, const uint32_t height)
{
uint32_t streamHeight = 0;
uint32_t streamWidth = 0;
AVPacket pkt;
uint32_t frameId = 0;
uint32_t channelId = 0;
AVFormatContext* pFormatCtx = avformat_alloc_context();
pFormatCtx = CreateFormatContext(filePath);
if (pFormatCtx == nullptr) {
return;
}
av_dump_format(pFormatCtx, 0, filePath.c_str(), 0);
DeviceContext context = {};
context.devId = 0;
DeviceManager::GetInstance()->SetDevice(context);
for (unsigned int i = 0; i < pFormatCtx->nb_streams; ++i) {
AVStream *inStream = pFormatCtx->streams[i];
if (inStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
streamHeight = inStream->codecpar->height;
streamWidth = inStream->codecpar->width;
if (streamHeight != height) {
LogError << "Video height " << streamHeight << " is not equal to the configuration height "
<< height << ".";
g_readVideoEnded = true;
return;
}
if (streamWidth != width) {
LogError << "Video width " << streamWidth << " is not equal to the configuration width "
<< width << ".";
g_readVideoEnded = true;
return;
}
}
}
while (!g_sendSignial && !g_readVideoEnded) {
EncodedFrame encodedFrame;
encodedFrame.channelId = channelId;
encodedFrame.frameId = frameId;
GetFrame(pkt, encodedFrame, pFormatCtx);
g_pullerToVdecQueue.Push(encodedFrame, true);
frameId += 1;
std::this_thread::sleep_for(std::chrono::milliseconds(FRAME_WAIT_TIME));
}
std::cout << "*********************StreamPullThread end*********************" << std::endl;
}
void VdecThread(VideoDecoder& videoDecoder)
{
while (!g_sendSignial) {
if (g_readVideoEnded && g_pullerToVdecQueue.GetSize() ==0) {
break;
}
EncodedFrame encodedFrame;
APP_ERROR ret = g_pullerToVdecQueue.Pop(encodedFrame, TIME_OUT);
if (ret != APP_ERR_OK || encodedFrame.data == nullptr) {
continue;
}
ret = videoDecoder.Decode(encodedFrame.data, encodedFrame.dataSize, encodedFrame.frameId,
static_cast<void*>(&g_vdecToVencQueue));
if (ret != APP_ERR_OK) {
LogError << "Decode failed.";
}
std::this_thread::sleep_for(std::chrono::milliseconds(FRAME_WAIT_TIME));
}
g_vdecEnded = true;
std::cout << "*********************VdecThread end*********************" << std::endl;
}
APP_ERROR VdecCallBack(MxBase::Image &decodedImage, uint32_t channelId, uint32_t frameId,
void *userData)
{
DecodedFrame decodedFrame{decodedImage, channelId, frameId};
BlockingQueue<DecodedFrame>* vdecToVencQueuePtr = static_cast<BlockingQueue<DecodedFrame>*>(userData);
if (vdecToVencQueuePtr == nullptr) {
LogError << "VideoDecoderCallback: g_vdecToVencQueue has been released.";
return APP_ERR_DVPP_INVALID_FORMAT;
}
vdecToVencQueuePtr->Push(decodedFrame, true);
return APP_ERR_OK;
}
void VencThread(VideoEncoder& videoEncoder)
{
while (!g_sendSignial) {
if (g_vdecEnded && g_vdecToVencQueue.GetSize() ==0) {
break;
}
DecodedFrame decodedFrame;
APP_ERROR ret = g_vdecToVencQueue.Pop(decodedFrame, TIME_OUT);
if (ret != APP_ERR_OK) {
continue;
}
ret = videoEncoder.Encode(decodedFrame.image, decodedFrame.frameId, static_cast<void*>(&g_vencToFileSaveQueue));
if (ret != APP_ERR_OK) {
LogError << "Encode failed.";
}
std::this_thread::sleep_for(std::chrono::milliseconds(FRAME_WAIT_TIME));
}
g_vencEnded = true;
std::cout << "*********************VencThread end*********************" << std::endl;
}
APP_ERROR VencCallBack(std::shared_ptr<uint8_t>& outDataPtr, uint32_t& outDataSize,
uint32_t& channelId, uint32_t& frameId, void* userData)
{
EncodedFrame encodedFrame {outDataPtr, outDataSize, channelId, frameId};
auto vencToFileSaveQueuePtr = static_cast<BlockingQueue<EncodedFrame>*>(userData);
if (vencToFileSaveQueuePtr == nullptr) {
LogError << "g_vencToFileSaveQueue has been released." << std::endl;
return APP_ERR_DVPP_INVALID_FORMAT;
}
vencToFileSaveQueuePtr->Push(encodedFrame, true);
return APP_ERR_OK;
}
void SaveFrameThread(StreamFormat streamFormat)
{
string savePath = DEFAULT_SAVED_FILE_PATH;
if (streamFormat == StreamFormat::H265_MAIN_LEVEL) {
savePath = savePath + ".h265";
} else {
savePath = savePath + ".h264";
}
FILE *fp = fopen(savePath.c_str(), "wb");
if (fp == nullptr) {
LogError << "Failed to open file.";
return;
}
bool mbFoundFirstIDR = false;
bool bIsIDR = false;
while (!g_sendSignial) {
if (g_vencEnded && g_vencToFileSaveQueue.GetSize() == 0) {
break;
}
EncodedFrame encodedFrame;
APP_ERROR ret = g_vencToFileSaveQueue.Pop(encodedFrame, TIME_OUT);
if (ret != APP_ERR_OK) {
continue;
}
bIsIDR = (encodedFrame.dataSize > 1);
if (!mbFoundFirstIDR) {
if (!bIsIDR) {
LogWarn << "Not bIsIDR!!!";
continue;
} else {
mbFoundFirstIDR = true;
}
}
if (fwrite(encodedFrame.data.get(), encodedFrame.dataSize, 1, fp) != 1) {
LogError << "write frame to file fail";
}
}
if (fclose(fp) != 0) {
LogError << "Failed to close file.";
}
std::cout << "*********************Save frame thread end*********************" << std::endl;
}
int main(int argc, char *argv[])
{
avformat_network_init();
if (MxInit() != APP_ERR_OK) {
LogError << "Fail to conduct MxInit.";
return APP_ERR_COMM_FAILURE;
}
if (signal(SIGINT, SigHandler) == SIG_ERR) {
LogError << "Fail to register SigHandler.";
return APP_ERR_COMM_FAILURE;
}
{
std::string filePath = "${filePath}";
int width = ${width};
int height = ${height};
VideoDecodeConfig vDecodeConfig;
vDecodeConfig.width = width;
vDecodeConfig.height = height;
vDecodeConfig.inputVideoFormat = StreamFormat::H264_MAIN_LEVEL;
vDecodeConfig.outputImageFormat = ImageFormat::YUV_SP_420;
vDecodeConfig.callbackFunc = VdecCallBack;
VideoDecoder videoDecoder = VideoDecoder(vDecodeConfig, DEFAULT_DEVICE_ID, DEFAULT_CHANNEL_ID);
VideoEncodeConfig vEncodeConfig;
vEncodeConfig.width = width;
vEncodeConfig.height = height;
vEncodeConfig.inputImageFormat = ImageFormat::YUV_SP_420;
vEncodeConfig.srcRate = ${fps};
vEncodeConfig.outputVideoFormat = StreamFormat::H264_MAIN_LEVEL;
vEncodeConfig.displayRate = ${fps};
vEncodeConfig.callbackFunc = VencCallBack;
VideoEncoder videoEncoder = VideoEncoder(vEncodeConfig, DEFAULT_DEVICE_ID, DEFAULT_CHANNEL_ID);
std::thread streamPullerThread = std::thread(StreamPullerThread, filePath, width, height);
std::cout << "*********************StreamPullerThread start*********************" << std::endl;
std::thread vdecThread = std::thread(VdecThread, std::ref(videoDecoder));
std::cout << "*********************VdecThread start*********************" << std::endl;
std::thread vencThread = std::thread(VencThread, std::ref(videoEncoder));
std::cout << "*********************VencThread start*********************" << std::endl;
std::thread saveFrameThread = std::thread(SaveFrameThread, vEncodeConfig.outputVideoFormat);
std::cout << "*********************SaveFrameThread start*********************" << std::endl;
streamPullerThread.join();
vdecThread.join();
vencThread.join();
saveFrameThread.join();
g_pullerToVdecQueue.Clear();
g_vdecToVencQueue.Clear();
g_vencToFileSaveQueue.Clear();
}
MxDeInit();
return 0;
}