/* * Copyright 2015 Rockchip Electronics Co. LTD * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "vpu_api.h" #define FOR_TEST_ENCODE 1 #define BSWAP32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #define DEMO_ERR_RET(err) do { ret = err; goto DEMO_OUT; } while (0) #define DECODE_ERR_RET(err) do { ret = err; goto DECODE_OUT; } while (0) #define ENCODE_ERR_RET(err) do { ret = err; goto ENCODE_OUT; } while (0) typedef enum VPU_API_DEMO_RET { VPU_DEMO_OK = 0, VPU_DEMO_PARSE_HELP_OK = 1, VPU_DEMO_ERROR_BASE = -100, ERROR_INVALID_PARAM = VPU_DEMO_ERROR_BASE - 1, ERROR_INVALID_STREAM = VPU_DEMO_ERROR_BASE - 2, ERROR_IO = VPU_DEMO_ERROR_BASE - 3, ERROR_MEMORY = VPU_DEMO_ERROR_BASE - 4, ERROR_INIT_VPU = VPU_DEMO_ERROR_BASE - 5, ERROR_VPU_DECODE = VPU_DEMO_ERROR_BASE - 90, } VPU_API_DEMO_RET; typedef struct VpuApiDemoCmdContext { RK_U32 width; RK_U32 height; CODEC_TYPE codec_type; OMX_RK_VIDEO_CODINGTYPE coding; char input_file[200]; char output_file[200]; RK_U8 have_input; RK_U8 have_output; RK_U32 record_frames; RK_S64 record_start_ms; } VpuApiDemoCmdContext_t; typedef struct VpuApiEncInput { EncInputStream_t stream; RK_U32 capability; } VpuApiEncInput; typedef struct VpuApiOptionInfo_t { const char* name; const char* argname; const char* help; } VpuApiOptionInfo; static VpuApiOptionInfo vpuApiCmd[] = { { "i", "input_file", "input bitstream file" }, { "o", "output_file", "output bitstream file, " }, { "w", "width", "the width of input bitstream" }, { "h", "height", "the height of input bitstream" }, { "t", "codec_type", "the codec type, dec: deoder, enc: encoder, default: decoder" }, { "coding", "coding_type", "encoding type of the bitstream" }, { "vframes", "number", "set the number of video frames to record" }, { "ss", "time_off", "set the start time offset, use Ms as the unit." }, }; static void *vpuapi_hdl = NULL; static VpuCodecContext_t *ctx; RK_S32 (*vpuapi_open_ctx)(VpuCodecContext_t **ctx); RK_S32 (*vpuapi_close_ctx)(VpuCodecContext_t **ctx); RK_S32 (*vpuapi_mem_link)(VPUMemLinear_t *p); RK_S32 (*vpuapi_mem_free)(VPUMemLinear_t *p); static void show_usage() { printf("usage: vpu_apiDemo [options] input_file, \n\n"); printf("Getting help:\n"); printf("-help --print options of vpu api demo\n"); } static RK_S32 show_help() { RK_S32 i; RK_S32 count = sizeof(vpuApiCmd) / sizeof(vpuApiCmd[0]); VpuApiOptionInfo *opt = vpuApiCmd; printf("usage: vpu_apiDemo [opt] input_file, \n\n"); for (i = 0; i < count; i++) printf("-%s %-16s\t%s\n", opt[i].name, opt[i].argname, opt[i].help); return 0; } static RK_S32 parse_options(int argc, char **argv, VpuApiDemoCmdContext_t *cmdCxt) { char *opt; RK_S32 optindex, handleoptions = 1, ret = 0; if ((argc < 2) || (cmdCxt == NULL)) { printf("vpu api demo, input parameter invalid\n"); show_usage(); return ERROR_INVALID_PARAM; } /* parse options */ optindex = 1; while (optindex < argc) { opt = argv[optindex++]; if (handleoptions && opt[0] == '-' && opt[1] != '\0') { if (opt[1] == '-') { if (opt[2] != '\0') { opt++; } else { handleoptions = 0; continue; } } opt++; switch (*opt) { case 'i': if (argv[optindex]) { memcpy(cmdCxt->input_file, argv[optindex], strlen(argv[optindex])); cmdCxt->input_file[strlen(argv[optindex])] = '\0'; cmdCxt->have_input = 1; } else { printf("input file is invalid\n"); ret = -1; goto PARSE_OPINIONS_OUT; } break; case 'o': if (argv[optindex]) { memcpy(cmdCxt->output_file, argv[optindex], strlen(argv[optindex])); cmdCxt->output_file[strlen(argv[optindex])] = '\0'; cmdCxt->have_output = 1; break; } else { printf("out file is invalid\n"); ret = -1; goto PARSE_OPINIONS_OUT; } case 'w': if (argv[optindex]) { cmdCxt->width = atoi(argv[optindex]); break; } else { printf("input width is invalid\n"); ret = -1; goto PARSE_OPINIONS_OUT; } case 'h': if ((*(opt + 1) != '\0') && !strncmp(opt, "help", 4)) { show_help(); ret = VPU_DEMO_PARSE_HELP_OK; goto PARSE_OPINIONS_OUT; } else if (argv[optindex]) { cmdCxt->height = atoi(argv[optindex]); } else { printf("input height is invalid\n"); ret = -1; goto PARSE_OPINIONS_OUT; } break; case 't': if (argv[optindex]) { cmdCxt->codec_type = atoi(argv[optindex]); break; } else { printf("input codec_type is invalid\n"); ret = -1; goto PARSE_OPINIONS_OUT; } default: if ((*(opt + 1) != '\0') && argv[optindex]) { if (!strncmp(opt, "coding", 6)) { printf("coding, argv[optindex]: %s", argv[optindex]); cmdCxt->coding = atoi(argv[optindex]); } else if (!strncmp(opt, "vframes", 7)) { cmdCxt->record_frames = atoi(argv[optindex]); } else if (!strncmp(opt, "ss", 2)) { cmdCxt->record_start_ms = atoi(argv[optindex]); } else { ret = -1; goto PARSE_OPINIONS_OUT; } } else { ret = -1; goto PARSE_OPINIONS_OUT; } break; } optindex += ret; } } PARSE_OPINIONS_OUT: if (ret < 0) { printf("vpu api demo, input parameter invalid\n"); show_usage(); return ERROR_INVALID_PARAM; } return ret; } static RK_S32 readBytesFromFile(RK_U8 *buf, RK_S32 aBytes, FILE *file) { RK_S32 ret = 0; if ((NULL == buf) || (NULL == file) || (0 == aBytes)) { return -1; } ret = (RK_S32)fread(buf, 1, aBytes, file); if (ret != aBytes) { printf("read %d bytes from file fail\n", aBytes); return -1; } return 0; } static RK_S32 vpu_encode_demo(VpuApiDemoCmdContext_t *cmd) { FILE *pInFile = NULL; FILE *pOutFile = NULL; RK_S32 nal = 0x00000001; RK_S32 fileSize; RK_S32 ret = 0; RK_S32 size; RK_U32 readOneFrameSize = 0; EncoderOut_t enc_out_yuv; EncoderOut_t *enc_out = NULL; VpuApiEncInput enc_in_strm; VpuApiEncInput *api_enc_in = &enc_in_strm; EncInputStream_t *enc_in = NULL; EncParameter_t *enc_param = NULL; RK_S64 fakeTimeUs = 0; RK_U32 w_align = 0; RK_U32 h_align = 0; int Format = ENC_INPUT_YUV420_PLANAR; if (cmd == NULL) { return -1; } if ((cmd->have_input == 0) || (cmd->width <= 0) || (cmd->height <= 0) || (cmd->coding <= OMX_RK_VIDEO_CodingAutoDetect)) { printf("Warning: missing needed parameters for vpu api demo\n"); } if (cmd->have_input) { printf("input bitstream w: %d, h: %d, coding: %d(%s), path: %s\n", cmd->width, cmd->height, cmd->coding, cmd->codec_type == CODEC_DECODER ? "decode" : "encode", cmd->input_file); pInFile = fopen(cmd->input_file, "rb"); if (pInFile == NULL) { printf("input file not exsist\n"); ENCODE_ERR_RET(ERROR_INVALID_PARAM); } } else { printf("please set input bitstream file\n"); ENCODE_ERR_RET(ERROR_INVALID_PARAM); } if (cmd->have_output) { printf("vpu api demo output file: %s\n", cmd->output_file); pOutFile = fopen(cmd->output_file, "wb"); if (pOutFile == NULL) { printf("can not write output file\n"); ENCODE_ERR_RET(ERROR_INVALID_PARAM); } } #ifdef FOR_TEST_ENCODE ctx = (struct VpuCodecContext *)malloc(sizeof(struct VpuCodecContext)); if (!ctx) { printf("Input context has not been properly allocated"); return -1; } memset(ctx, 0, sizeof(struct VpuCodecContext)); ctx->videoCoding = OMX_RK_VIDEO_CodingAVC; ctx->codecType = CODEC_ENCODER; ctx->width = cmd->width; ctx->height = cmd->height; #endif fseek(pInFile, 0L, SEEK_END); fileSize = ftell(pInFile); fseek(pInFile, 0L, SEEK_SET); memset(&enc_in_strm, 0, sizeof(VpuApiEncInput)); enc_in = &enc_in_strm.stream; enc_in->buf = NULL; memset(&enc_out_yuv, 0, sizeof(EncoderOut_t)); enc_out = &enc_out_yuv; enc_out->data = (RK_U8 *)malloc(cmd->width * cmd->height); if (enc_out->data == NULL) { ENCODE_ERR_RET(ERROR_MEMORY); } ret = vpuapi_open_ctx(&ctx); if (ret || (ctx == NULL)) { ENCODE_ERR_RET(ERROR_MEMORY); } /* ** now init vpu api context. codecType, codingType, width ,height ** are all needed before init. */ ctx->codecType = cmd->codec_type; ctx->videoCoding = cmd->coding; ctx->width = cmd->width; ctx->height = cmd->height; ctx->no_thread = 1; ctx->private_data = malloc(sizeof(EncParameter_t)); memset(ctx->private_data, 0, sizeof(EncParameter_t)); enc_param = (EncParameter_t *)ctx->private_data; enc_param->width = cmd->width; enc_param->height = cmd->height; enc_param->format = ENC_INPUT_YUV420_PLANAR; enc_param->rc_mode = 0; enc_param->bitRate = 4000000; enc_param->framerate = 25; enc_param->enableCabac = 1; enc_param->cabacInitIdc = 0; enc_param->intraPicRate = 30; enc_param->profileIdc = 100; enc_param->levelIdc = 40; if ((ret = ctx->init(ctx, NULL, 0)) != 0) { printf("init vpu api context fail, ret: 0x%X\n", ret); ENCODE_ERR_RET(ERROR_INIT_VPU); } /* ** init of VpuCodecContext while running encode, it returns ** sps and pps of encoder output, you need to save sps and pps ** after init. */ printf("encode init ok, sps len: %d\n", ctx->extradata_size); if (pOutFile && (ctx->extradata_size > 0)) { printf("dump %d bytes enc output stream to file\n", ctx->extradata_size); /* save sps and pps */ fwrite(ctx->extradata, 1, ctx->extradata_size, pOutFile); fflush(pOutFile); } ret = ctx->control(ctx, VPU_API_ENC_SETFORMAT, &Format); if (ret) printf("VPU_API_ENC_SETFORMAT ret %d\n", ret); ret = ctx->control(ctx, VPU_API_ENC_GETCFG, enc_param); if (ret) printf("VPU_API_ENC_GETCFG ret %d\n", ret); enc_param->rc_mode = 1; ret = ctx->control(ctx, VPU_API_ENC_SETCFG, enc_param); if (ret) printf("VPU_API_ENC_SETCFG ret %d\n", ret); /* ** vpu api encode process. */ printf("init vpu api context ok, input yuv stream file size: %d\n", fileSize); w_align = ((ctx->width + 15) & (~15)); h_align = ((ctx->height + 15) & (~15)); size = w_align * h_align * 3 / 2; readOneFrameSize = ctx->width * ctx->height * 3 / 2; printf("%d %d %d %d %d", ctx->width, ctx->height, w_align, h_align, size); nal = BSWAP32(nal); do { if (ftell(pInFile) >= fileSize) { printf("read end of file, complete\n"); break; } if (enc_in && (enc_in->size == 0)) { if (enc_in->buf == NULL) { enc_in->buf = (RK_U8 *)(malloc)(size); if (enc_in->buf == NULL) { ENCODE_ERR_RET(ERROR_MEMORY); } api_enc_in->capability = size; } if (api_enc_in->capability < ((RK_U32)size)) { enc_in->buf = (RK_U8 *)(realloc)((void *)(enc_in->buf), size); if (enc_in->buf == NULL) { ENCODE_ERR_RET(ERROR_MEMORY); } api_enc_in->capability = size; } if (readBytesFromFile(enc_in->buf, readOneFrameSize, pInFile)) { break; } else { enc_in->size = size; enc_in->timeUs = fakeTimeUs; fakeTimeUs += 40000; } printf("read one frame, size: %d, timeUs: %lld, filePos: %ld\n", enc_in->size, enc_in->timeUs, ftell(pInFile)); } if ((ret = ctx->encode(ctx, enc_in, enc_out)) < 0) { ENCODE_ERR_RET(ERROR_VPU_DECODE); } else { enc_in->size = 0; // TODO encode completely, and set enc_in->size to 0 printf("vpu encode one frame, out len: %d, left size: %d\n", enc_out->size, enc_in->size); /* ** encoder output stream is raw bitstream, you need to add nal ** head by yourself. */ if ((enc_out->size) && (enc_out->data)) { if (pOutFile) { printf("dump %d bytes enc output stream to file\n", enc_out->size); //fwrite((RK_U8*)&nal, 1, 4, pOutFile); // because output stream have start code, so here mask this code fwrite(enc_out->data, 1, enc_out->size, pOutFile); fflush(pOutFile); } enc_out->size = 0; } } usleep(3000); } while (1); ENCODE_OUT: if (enc_in && enc_in->buf) { free(enc_in->buf); enc_in->buf = NULL; } if (enc_out && (enc_out->data)) { free(enc_out->data); enc_out->data = NULL; } if (ctx) { if (ctx->private_data) { free(ctx->private_data); ctx->private_data = NULL; } vpuapi_close_ctx(&ctx); ctx = NULL; } if (pInFile) { fclose(pInFile); pInFile = NULL; } if (pOutFile) { fclose(pOutFile); pOutFile = NULL; } if (ret) { printf("encode demo fail, err: %d\n", ret); } else { printf("encode demo complete OK.\n"); } return ret; } static RK_S32 vpu_decode_demo(VpuApiDemoCmdContext_t *cmd) { FILE *pInFile = NULL; FILE *pOutFile = NULL; RK_S32 fileSize = 0, pkt_size = 0; RK_S32 ret = 0; RK_U32 frame_count = 0; DecoderOut_t decOut; VideoPacket_t demoPkt; VideoPacket_t *pkt = NULL; DecoderOut_t *pOut = NULL; VPU_FRAME *frame = NULL; RK_S64 fakeTimeUs = 0; RK_U8 *pExtra = NULL; RK_U32 extraSize = 0; RK_U32 wAlign16 = 0; RK_U32 hAlign16 = 0; RK_U32 frameSize = 0; if (cmd == NULL) { return -1; } if ((cmd->have_input == 0) || (cmd->width <= 0) || (cmd->height <= 0) || (cmd->coding <= OMX_RK_VIDEO_CodingAutoDetect)) { printf("Warning: missing needed parameters for vpu api demo\n"); } if (cmd->have_input) { printf("input bitstream w: %d, h: %d, coding: %d(%s), path: %s\n", cmd->width, cmd->height, cmd->coding, cmd->codec_type == CODEC_DECODER ? "decode" : "encode", cmd->input_file); pInFile = fopen(cmd->input_file, "rb"); if (pInFile == NULL) { printf("input file not exsist\n"); DECODE_ERR_RET(ERROR_INVALID_PARAM); } } else { printf("please set input bitstream file\n"); DECODE_ERR_RET(ERROR_INVALID_PARAM); } if (cmd->have_output) { printf("vpu api demo output file: %s\n", cmd->output_file); pOutFile = fopen(cmd->output_file, "wb"); if (pOutFile == NULL) { printf("can not write output file\n"); DECODE_ERR_RET(ERROR_INVALID_PARAM); } if (cmd->record_frames == 0) cmd->record_frames = 5; } fseek(pInFile, 0L, SEEK_END); fileSize = ftell(pInFile); fseek(pInFile, 0L, SEEK_SET); memset(&demoPkt, 0, sizeof(VideoPacket_t)); pkt = &demoPkt; pkt->data = NULL; pkt->pts = VPU_API_NOPTS_VALUE; pkt->dts = VPU_API_NOPTS_VALUE; memset(&decOut, 0, sizeof(DecoderOut_t)); pOut = &decOut; ret = vpuapi_open_ctx(&ctx); if (ret || (ctx == NULL)) { DECODE_ERR_RET(ERROR_MEMORY); } /* ** read codec extra data from input stream file. */ if (readBytesFromFile((RK_U8 *)(&extraSize), 4, pInFile)) { DECODE_ERR_RET(ERROR_IO); } printf("codec extra data size: %d\n", extraSize); pExtra = (RK_U8 *)(malloc)(extraSize); if (pExtra == NULL) { DECODE_ERR_RET(ERROR_MEMORY); } memset(pExtra, 0, extraSize); if (readBytesFromFile(pExtra, extraSize, pInFile)) { DECODE_ERR_RET(ERROR_IO); } /* ** now init vpu api context. codecType, codingType, width ,height ** are all needed before init. */ ctx->codecType = cmd->codec_type; ctx->videoCoding = cmd->coding; ctx->width = cmd->width; ctx->height = cmd->height; ctx->no_thread = 1; if ((ret = ctx->init(ctx, pExtra, extraSize)) != 0) { printf("init vpu api context fail, ret: 0x%X\n", ret); DECODE_ERR_RET(ERROR_INIT_VPU); } /* ** vpu api decoder process. */ printf("init vpu api context ok, fileSize: %d\n", fileSize); do { if (ftell(pInFile) >= fileSize) { printf("read end of file, complete\n"); break; } if (pkt && (pkt->size == 0)) { if (readBytesFromFile((RK_U8 *)(&pkt_size), 4, pInFile)) { break; } if (pkt->data == NULL) { pkt->data = (RK_U8 *)(malloc)(pkt_size); if (pkt->data == NULL) { DECODE_ERR_RET(ERROR_MEMORY); } pkt->capability = pkt_size; } if (pkt->capability < ((RK_U32)pkt_size)) { pkt->data = (RK_U8 *)(realloc)((void *)(pkt->data), pkt_size); if (pkt->data == NULL) { DECODE_ERR_RET(ERROR_MEMORY); } pkt->capability = pkt_size; } if (readBytesFromFile(pkt->data, pkt_size, pInFile)) { break; } else { pkt->size = pkt_size; pkt->pts = fakeTimeUs; fakeTimeUs += 40000; } printf("read one packet, size: %d, pts: %lld, filePos: %ld\n", pkt->size, pkt->pts, ftell(pInFile)); } /* note: must set out put size to 0 before do decoder. */ pOut->size = 0; if (ctx->decode_sendstream(ctx, pkt) != 0) { printf("send packet failed"); DECODE_ERR_RET(ERROR_VPU_DECODE); } if ((ret = ctx->decode_getframe(ctx, pOut)) != 0) { printf("get decoded data failed\n"); DECODE_ERR_RET(ERROR_VPU_DECODE); } else { printf("vpu decode one frame, out len: %d, left size: %d\n", pOut->size, pkt->size); /* ** both virtual and physical address of the decoded frame are contained ** in structure named VPU_FRAME, if you want to use virtual address, make ** sure you have done VPUMemLink before. */ if ((pOut->size) && (pOut->data)) { frame = (VPU_FRAME *)(pOut->data); vpuapi_mem_link(&frame->vpumem); wAlign16 = ((frame->DisplayWidth + 15) & (~15)); hAlign16 = ((frame->DisplayHeight + 15) & (~15)); frameSize = wAlign16 * hAlign16 * 3 / 2; if (pOutFile && (frame_count++ < cmd->record_frames)) { printf("write %d frame(yuv420sp) data, %d bytes to file\n", frame_count, frameSize); fwrite((RK_U8 *)(frame->vpumem.vir_addr), 1, frameSize, pOutFile); fflush(pOutFile); } /* ** remember use VPUFreeLinear to free, other wise memory leak will ** give you a surprise. */ vpuapi_mem_free(&frame->vpumem); // NOTE: pOut->data is malloc from vpu_api we need to free it. free(pOut->data); pOut->data = NULL; pOut->size = 0; } } usleep(3000); } while (!(ctx->decoder_err)); DECODE_OUT: if (pkt && pkt->data) { free(pkt->data); pkt->data = NULL; } if (pOut && (pOut->data)) { free(pOut->data); pOut->data = NULL; } if (pExtra) { free(pExtra); pExtra = NULL; } if (ctx) { vpuapi_close_ctx(&ctx); ctx = NULL; } if (pInFile) { fclose(pInFile); pInFile = NULL; } if (pOutFile) { fclose(pOutFile); pOutFile = NULL; } if (ret) { printf("decode demo fail, err: %d\n", ret); } else { printf("encode demo complete OK.\n"); } return ret; } /* vpu_api is only on Android platform */ int main(int argc, char **argv) { RK_S32 ret = 0; VpuApiDemoCmdContext_t demoCmdCtx; VpuApiDemoCmdContext_t *cmd = NULL; printf("/******* vpu api demo in *******/\n"); if (argc == 1) { show_usage(); return 0; } /* open library for access */ vpuapi_hdl = dlopen("libvpu.so", RTLD_LAZY | RTLD_GLOBAL); if (NULL == vpuapi_hdl) { printf("failed to open libvpu.so\n"); ret = -1; goto DEMO_OUT; } vpuapi_open_ctx = (RK_S32 (*)(VpuCodecContext_t **ctx))dlsym(vpuapi_hdl, "vpu_open_context"); vpuapi_close_ctx = (RK_S32 (*)(VpuCodecContext_t **ctx))dlsym(vpuapi_hdl, "vpu_close_context"); vpuapi_mem_link = (RK_S32 (*)(VPUMemLinear_t * p))dlsym(vpuapi_hdl, "VPUMemLink"); vpuapi_mem_free = (RK_S32 (*)(VPUMemLinear_t * p))dlsym(vpuapi_hdl, "VPUFreeLinear"); if (NULL == vpuapi_open_ctx || NULL == vpuapi_close_ctx || NULL == vpuapi_mem_link || NULL == vpuapi_mem_free) { printf("failed to open vpu_open_context %p vpu_close_context %p\n", vpuapi_open_ctx, vpuapi_close_ctx); printf("failed to open VPUMemLink %p VPUFreeLinear %p\n", vpuapi_mem_link, vpuapi_mem_free); ret = -1; goto DEMO_OUT; } printf("get vpuapi handle %p vpu_open_context %p vpu_close_context %p\n", vpuapi_hdl, vpuapi_open_ctx, vpuapi_close_ctx); cmd = &demoCmdCtx; memset(cmd, 0, sizeof(VpuApiDemoCmdContext_t)); cmd->codec_type = CODEC_DECODER; if ((ret = parse_options(argc, argv, cmd)) != 0) { if (ret == VPU_DEMO_PARSE_HELP_OK) { return 0; } printf("parse_options fail\n\n"); show_usage(); DEMO_ERR_RET(ERROR_INVALID_PARAM); } switch (cmd->codec_type) { case CODEC_DECODER : { ret = vpu_decode_demo(cmd); } break; case CODEC_ENCODER : { ret = vpu_encode_demo(cmd); } break; default : { ret = ERROR_INVALID_PARAM; } break; } DEMO_OUT: if (ret) { printf("vpu api demo fail, err: %d\n", ret); if (vpuapi_hdl) { dlclose(vpuapi_hdl); vpuapi_hdl = NULL; } } else { printf("vpu api demo complete OK.\n"); } return ret; }