/************************************************************************************************** * * Copyright (c) 2019-2024 Axera Semiconductor Co., Ltd. All Rights Reserved. * * This source file is the property of Axera Semiconductor Co., Ltd. and * may not be copied or distributed in any isomorphic form without the prior * written consent of Axera Semiconductor Co., Ltd. * **************************************************************************************************/ #include #include #include #include #include #include #include #include "axcl.h" #include "cmdline.h" #include "demux/ffmpeg.hpp" #include "dma_buffer.hpp" #include "event.hpp" #include "sample_vdec.hpp" #include "sys/sample_sys.hpp" #include "threadx.hpp" #include "utils/def.h" #include "utils/logger.h" #define MAX_STREAM_COUNT (32) static int g_exit = 0; static void signal_handler(int s) { printf("\n====================== caught signal: %d ======================\n", s); g_exit = 1; } std::string output_to_file; static void on_receive_demux_stream_data(const struct stream_data *data, uint64_t userdata); static SAMPLE_VDEC_ATTR sample_get_vdec_attr_from_stream_info(const struct stream_info *info); static void sample_get_decoded_image_thread(AX_VDEC_GRP grp, int32_t device_id, axcl::event *eof_event, SAMPLE_VDEC_CHN_INFO chn_info, int32_t dump); static AX_S32 sample_vdec_set_attr(SAMPLE_VDEC_ATTR *vdec_attr, int32_t chn_id, int32_t w, int32_t h); int main(int argc, char *argv[]) { SAMPLE_LOG_I("============== %s sample started %s %s ==============\n", AXCL_BUILD_VERSION, __DATE__, __TIME__); signal(SIGINT, signal_handler); cmdline::parser a; a.add("url", 'i', "mp4|.264|.265 file path", true); a.add("device", 'd', "device index from 0 to connected device num - 1", false, 0, cmdline::range(0, AXCL_MAX_DEVICE_COUNT - 1)); a.add("count", '\0', "grp count", false, 1, cmdline::range(1, MAX_STREAM_COUNT)); a.add("json", '\0', "axcl.json path", false, "./axcl.json"); a.add("width", 'w', "frame width", false, 1920, cmdline::range(AX_VDEC_MIN_WIDTH, AX_VDEC_MAX_WIDTH)); a.add("height", 'h', "frame height", false, 1080, cmdline::range(AX_VDEC_MIN_HEIGHT, AX_VDEC_MAX_HEIGHT)); a.add("VdChn", '\0', "channel id", false, 0); a.add("yuv", '\0', "transfer nv12 from device", false, 0); a.add("output", 'o', "output to file", false, ""); a.parse_check(argc, argv); const uint32_t device_index = a.get("device"); const std::string url = a.get("url"); const int32_t count = a.get("count"); const std::string json = a.get("json"); int32_t w = a.get("width"); int32_t h = a.get("height"); int32_t chn_id = a.get("VdChn"); const int32_t dump = a.get("yuv"); output_to_file = a.get("output"); /* step01: axcl initialize */ SAMPLE_LOG_I("json: %s", json.c_str()); if (axclError ret = axclInit(json.c_str()); AXCL_SUCC != ret) { SAMPLE_LOG_E("axcl init fail, ret = 0x%x", ret); return ret; } axclrtDeviceList device_list; if (axclError ret = axclrtGetDeviceList(&device_list); AXCL_SUCC != ret || 0 == device_list.num) { SAMPLE_LOG_E("no device is connected"); axclFinalize(); return ret; } if (device_index >= device_list.num) { SAMPLE_LOG_E("device index %d is out of connected device num %d", device_index, device_list.num); axclFinalize(); return 1; } const int32_t device_id = device_list.devices[device_index]; SAMPLE_LOG_I("device index: %d, bus number: %d", device_index, device_id); /* step02: active device */ if (axclError ret = axclrtSetDevice(device_id); AXCL_SUCC != ret) { SAMPLE_LOG_E("active device, ret = 0x%x", ret); axclFinalize(); return ret; } ffmpeg_demuxer demuxers[MAX_STREAM_COUNT]; /* delegating demuxer memory destorier by smart pointer */ std::function demux_deleter = [](void *p) { ffmpeg_destory_demuxer(static_cast(p)); }; std::unique_ptr> demuxers_sp[MAX_STREAM_COUNT]; /* step03: create ffmpeg demuxers */ for (int32_t i = 0; i < count; ++i) { const AX_VDEC_GRP grp = static_cast(i); if (0 != ffmpeg_create_demuxer(&demuxers[i], url.c_str(), grp, device_id, {on_receive_demux_stream_data}, 0)) { axclrtResetDevice(device_id); axclFinalize(); return -1; } demuxers_sp[i] = std::unique_ptr>(demuxers[i], demux_deleter); } /* step04: init sys module */ SAMPLE_LOG_I("init sys"); if (AX_S32 ret = sample_sys_init(); 0 != ret) { SAMPLE_LOG_E("init sys, ret = 0x%x", ret); axclrtResetDevice(device_id); axclFinalize(); return ret; } /* step05: init vdec module */ SAMPLE_LOG_I("init vdec"); if (AX_S32 ret = sample_vdec_init(); 0 != ret) { SAMPLE_LOG_E("init vdec, ret = 0x%x", ret); sample_sys_deinit(); axclrtResetDevice(device_id); axclFinalize(); return ret; } /* step06: start vdec */ axcl::threadx decode_threads[MAX_STREAM_COUNT]; axcl::event decode_eof_events[MAX_STREAM_COUNT]; for (int32_t i = 0; i < count; ++i) { const AX_VDEC_GRP grp = static_cast(i); SAMPLE_LOG_I("start decoder %d", grp); SAMPLE_VDEC_ATTR attr = sample_get_vdec_attr_from_stream_info(ffmpeg_get_stream_info(demuxers[i])); if (chn_id) { sample_vdec_set_attr(&attr, chn_id, w, h); } if (AX_S32 ret = sample_vdec_start(grp, &attr); 0 != ret) { SAMPLE_LOG_E("start vdec %d fail, ret = 0x%x", grp, ret); for (int32_t j = 0; j < i; ++j) { sample_vdec_stop(j); } sample_vdec_deinit(); sample_sys_deinit(); axclrtResetDevice(device_id); axclFinalize(); } /* step07: start decoded get thread */ char name[32]; SAMPLE_VDEC_CHN_INFO chn_info; memset(&chn_info, 0, sizeof(chn_info)); chn_info.u32ChnId = chn_id; for (int32_t j = 0; j < AX_VDEC_MAX_CHN_NUM; ++j) { if (attr.chn_attr[j].enable) { chn_info.u32PicWidth = attr.chn_attr[j].attr.u32PicWidth; chn_info.u32PicHeight = attr.chn_attr[j].attr.u32PicHeight; break; } } sprintf(name, "decode%d", grp); decode_threads[i].start(name, sample_get_decoded_image_thread, grp, device_id, &decode_eof_events[i], chn_info, dump); /* step08: start to demux video */ SAMPLE_LOG_I("start demuxer %d", i); ffmpeg_start_demuxer(demuxers[i]); } int32_t eofs[MAX_STREAM_COUNT]; memset(eofs, 0, sizeof(eofs)); int32_t eof_count = 0; while (!g_exit) { /* wait video eof */ for (int32_t i = 0; i < count; ++i) { if (eofs[i]) { continue; } if (0 == ffmpeg_wait_demuxer_eof(demuxers[i], 0)) { if (!decode_eof_events[i].wait(1000)) { continue; } const AX_VDEC_GRP grp = static_cast(i); SAMPLE_LOG_I("stop decoder %d", grp); sample_vdec_stop(grp); eofs[i] = 1; ++eof_count; SAMPLE_LOG_I("decoder %d is eof", grp); } } if (eof_count == count) { /* all streams and decoders are eof */ g_exit = 1; break; } std::this_thread::sleep_for(std::chrono::seconds(1)); } /* step09: stop vdec and demuxer */ for (int32_t i = 0; i < count; ++i) { /** * bugfix: * Stop vdec first; otherwise `sample_vdec_send_stream(-1)` may hang. */ if (!eofs[i]) { const AX_VDEC_GRP grp = static_cast(i); SAMPLE_LOG_I("stop decoder %d", grp); sample_vdec_stop(grp); } SAMPLE_LOG_I("stop demuxer %d", i); ffmpeg_stop_demuxer(demuxers[i]); decode_threads[i].stop(); decode_threads[i].join(); } /* step10: deinit vdec module */ SAMPLE_LOG_I("deinit vdec"); sample_vdec_deinit(); /* step11: deinit sys module */ SAMPLE_LOG_I("deinit sys"); sample_sys_deinit(); /* step12: deinit axcl */ SAMPLE_LOG_I("axcl deinit"); axclrtResetDevice(device_id); axclFinalize(); SAMPLE_LOG_I("============== %s sample exited %s %s ==============\n", AXCL_BUILD_VERSION, __DATE__, __TIME__); return 0; } static SAMPLE_VDEC_ATTR sample_get_vdec_attr_from_stream_info(const struct stream_info *info) { SAMPLE_LOG_I("stream info: %dx%d payload %d fps %d", info->video.width, info->video.height, info->video.payload, info->video.fps); SAMPLE_VDEC_ATTR vdec_attr; memset(&vdec_attr, 0, sizeof(vdec_attr)); vdec_attr.decoded_mode = VIDEO_DEC_MODE_IPB; vdec_attr.output_order = AX_VDEC_OUTPUT_ORDER_DEC; vdec_attr.display_mode = AX_VDEC_DISPLAY_MODE_PLAYBACK; AX_VDEC_GRP_ATTR_T &grp_attr = vdec_attr.grp_attr; grp_attr.enCodecType = info->video.payload; grp_attr.enInputMode = AX_VDEC_INPUT_MODE_FRAME; grp_attr.u32MaxPicWidth = ALIGN_UP(info->video.width, 16); grp_attr.u32MaxPicHeight = ALIGN_UP(info->video.height, 16); grp_attr.u32StreamBufSize = grp_attr.u32MaxPicWidth * grp_attr.u32MaxPicHeight * 2; grp_attr.bSdkAutoFramePool = AX_TRUE; vdec_attr.chn_attr[0].enable = AX_TRUE; vdec_attr.chn_attr[1].enable = AX_FALSE; vdec_attr.chn_attr[2].enable = AX_FALSE; AX_VDEC_CHN_ATTR_T &chn_attr = vdec_attr.chn_attr[0].attr; chn_attr.u32PicWidth = info->video.width; chn_attr.u32PicHeight = info->video.height; chn_attr.u32FrameStride = ALIGN_UP(chn_attr.u32PicWidth, VDEC_STRIDE_ALIGN); chn_attr.u32OutputFifoDepth = 3; chn_attr.u32FrameBufCnt = 8; chn_attr.stCompressInfo.enCompressMode = AX_COMPRESS_MODE_NONE; chn_attr.stCompressInfo.u32CompressLevel = 0; chn_attr.enOutputMode = AX_VDEC_OUTPUT_ORIGINAL; chn_attr.enImgFormat = AX_FORMAT_YUV420_SEMIPLANAR; chn_attr.u32FrameBufSize = sample_vdec_calc_blk_size(chn_attr.u32PicWidth, chn_attr.u32PicHeight, info->video.payload, &chn_attr.stCompressInfo, chn_attr.enImgFormat); return vdec_attr; } static AX_S32 sample_vdec_set_attr(SAMPLE_VDEC_ATTR *vdec_attr, int32_t chn_id, int32_t w, int32_t h) { AX_VDEC_GRP_ATTR_T &grp_attr = vdec_attr->grp_attr; vdec_attr->chn_attr[0].enable = AX_FALSE; vdec_attr->chn_attr[chn_id].enable = AX_TRUE; AX_VDEC_CHN_ATTR_T &chn_attr = vdec_attr->chn_attr[chn_id].attr; chn_attr.u32PicWidth = w; chn_attr.u32PicHeight = h; chn_attr.u32FrameStride = ALIGN_UP(chn_attr.u32PicWidth, VDEC_STRIDE_ALIGN); chn_attr.u32OutputFifoDepth = 3; chn_attr.u32FrameBufCnt = 8; chn_attr.stCompressInfo.enCompressMode = AX_COMPRESS_MODE_NONE; chn_attr.stCompressInfo.u32CompressLevel = 0; chn_attr.enOutputMode = AX_VDEC_OUTPUT_SCALE; chn_attr.enImgFormat = AX_FORMAT_YUV420_SEMIPLANAR; chn_attr.u32FrameBufSize = sample_vdec_calc_blk_size(chn_attr.u32PicWidth, chn_attr.u32PicHeight, grp_attr.enCodecType, &chn_attr.stCompressInfo, chn_attr.enImgFormat); return 0; } void NV12toI420_stride( const uint8_t* nv12_ptr, // NV12原始数据指针 uint8_t* i420_ptr, // I420输出数据指针 int width, // 图像有效宽度 int height, // 图像有效高度 int picStride // 每行实际存储字节数(对齐) ) { int y_size = width * height; int uv_size = (width / 2) * (height / 2); const uint8_t* src_y = nv12_ptr; const uint8_t* src_uv = nv12_ptr + picStride * height; uint8_t* dst_y = i420_ptr; uint8_t* dst_u = i420_ptr + y_size; uint8_t* dst_v = i420_ptr + y_size + uv_size; // 1. 拷贝Y分量 for (int y = 0; y < height; y++) { memcpy(dst_y + y * width, src_y + y * picStride, width); } // 2. 拆分UV分量为U、V分量 for (int j = 0; j < height / 2; j++) { const uint8_t* uv_line = src_uv + j * picStride; for (int i = 0; i < width / 2; i++) { dst_u[j * (width / 2) + i] = uv_line[2 * i]; // U dst_v[j * (width / 2) + i] = uv_line[2 * i + 1]; // V } } } static void sample_get_decoded_image_thread(AX_VDEC_GRP grp, int32_t device_id, axcl::event *eof_event, SAMPLE_VDEC_CHN_INFO chn_info, int32_t dump) { SAMPLE_LOG_I("[decoder %2d] decode thread +++", grp); /* step01: create thread context */ axclrtContext context; if (axclError ret = axclrtCreateContext(&context, device_id); AXCL_SUCC != ret) { return; } const AX_VDEC_CHN chn = chn_info.u32ChnId; AX_S32 ret; const size_t size = ALIGN_UP(chn_info.u32PicWidth, VDEC_STRIDE_ALIGN) * chn_info.u32PicHeight * 3 / 2; dma_buffer allocator(device_id); if (dump) { if (size <= 0) { axclrtDestroyContext(context); SAMPLE_LOG_E("[decoder %2d] decode nv12 frame size = %ld", grp, size); return; } if (!allocator.alloc(size)) { SAMPLE_LOG_E("[decoder %2d] alloc cma mem size %ld fail", grp, size); axclrtDestroyContext(context); return; } SAMPLE_LOG_I("[decoder %2d] alloc cma mem size %ld", grp, size); } const struct dma_mem &mem = allocator.get(); FILE *output_file = NULL; if(!output_to_file.empty()) { output_file = fopen(output_to_file.c_str(), "wb"); } AX_U64 count = 0; AX_VIDEO_FRAME_INFO_T frame; memset(&frame, 0, sizeof(frame)); while (!g_exit) { /* step02: get decoded image */ ret = sample_vdec_get_frame(grp, chn, &frame, -1); if (0 != ret) { if (AX_ERR_VDEC_UNEXIST == ret) { SAMPLE_LOG_I("[decoder %2d] grp is destoried", grp); break; } else if (AX_ERR_VDEC_STRM_ERROR == ret) { SAMPLE_LOG_W("[decoder %2d] stream is undecodeable", grp); continue; } else if (AX_ERR_VDEC_FLOW_END == ret) { SAMPLE_LOG_W("[decoder %2d] flow end", grp); eof_event->set(); break; } else { if (g_exit) { break; } SAMPLE_LOG_E("[decoder %2d] get frame fail, ret = 0x%x", grp, ret); continue; } } /* TODO: */ if (0 == ret) { ++count; if (dump) { if (axclError Ret = axclrtMemcpy(reinterpret_cast(mem.blks[0].phy), (void*)frame.stVFrame.u64PhyAddr[0], size, AXCL_MEMCPY_DEVICE_TO_HOST_PHY); AXCL_SUCC != Ret) { SAMPLE_LOG_E("axclrt memcpy device phy to host phy error grp:%d, chn:%d", grp, chn); } } // int data_size = chn_info.u32PicWidth * chn_info.u32PicHeight * 3 / 2; SAMPLE_LOG_I("[decoder %d] got frame %lld, %dx%d stride %d size %u, pts %lld, phy %llx", grp, frame.stVFrame.u64SeqNum, frame.stVFrame.u32Width, frame.stVFrame.u32Height, frame.stVFrame.u32PicStride[0], frame.stVFrame.u32FrameSize, frame.stVFrame.u64PTS, frame.stVFrame.u64PhyAddr[0]); if(output_file) { std::vector yuv_buf(frame.stVFrame.u32FrameSize); if (axclError Ret = axclrtMemcpy((void*)yuv_buf.data(), reinterpret_cast(frame.stVFrame.u64PhyAddr[0]),frame.stVFrame.u32FrameSize, AXCL_MEMCPY_DEVICE_TO_HOST); AXCL_SUCC != Ret) { SAMPLE_LOG_E("axclrt memcpy device phy to host phy error grp:%d, chn:%d", grp, chn); } std::vector i420_buf((frame.stVFrame.u32Height * 3 / 2)*frame.stVFrame.u32Width); NV12toI420_stride(yuv_buf.data(), i420_buf.data(), frame.stVFrame.u32Width, frame.stVFrame.u32Height, frame.stVFrame.u32PicStride[0]); // for(int y = 0; y < frame.stVFrame.u32Height * 3 / 2; y++) // { // fwrite(yuv_buf.data() + y * frame.stVFrame.u32PicStride[0], 1, frame.stVFrame.u32Width, output_file); // } // for(int y = 0; y < frame.stVFrame.u32Height; y++) // { // fwrite(yuv_buf.data() + y * frame.stVFrame.u32PicStride[0], 1, frame.stVFrame.u32Width, output_file); // } // for(int y = frame.stVFrame.u32Height; y < frame.stVFrame.u32Height * 3 / 2; y++) // { // fwrite(yuv_buf.data() + y * frame.stVFrame.u32PicStride[0], 1, frame.stVFrame.u32Width, output_file); // } fwrite(i420_buf.data(), 1, i420_buf.size(), output_file); } /* step03: release decoded image */ sample_vdec_release_frame(grp, chn, &frame); } } if(output_file) { fclose(output_file); } SAMPLE_LOG_I("[decoder %2d] total decode %lld frames", grp, count); /* step04: destory thread context */ axclrtDestroyContext(context); allocator.free(); SAMPLE_LOG_I("[decoder %2d] dfecode thread ---", grp); } static void on_receive_demux_stream_data(const struct stream_data *data, uint64_t userdata) { AX_VDEC_GRP grp = static_cast(data->cookie); AX_VDEC_STREAM_T stream; memset(&stream, 0, sizeof(stream)); stream.u64PTS = data->video.pts; stream.u32StreamPackLen = data->video.size; stream.pu8Addr = data->video.data; stream.bEndOfFrame = AX_TRUE; if (0 == data->video.size) { stream.bEndOfStream = AX_TRUE; } AX_S32 ret = sample_vdec_send_stream(grp, &stream, -1); if (0 != ret && !g_exit) { SAMPLE_LOG_E("[decoder %2d] send stream (id: %ld, size: %u) fail, ret = 0x%x", grp, data->video.seq_num, data->video.size, ret); } }