| @@ -590,6 +590,13 @@ static void record_video_frame(FFPlayer *ffp, AVFrame *frame) | |||
| 590 | frData.data0 = (uint8_t *)malloc((size_t)frame->linesize[0] * frame->height); | 590 | frData.data0 = (uint8_t *)malloc((size_t)frame->linesize[0] * frame->height); |
| 591 | frData.data1 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2); | 591 | frData.data1 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2); |
| 592 | frData.data2 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2); | 592 | frData.data2 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2); |
| 593 | + if (!frData.data0 || !frData.data1 || !frData.data2) { | ||
| 594 | + LOGE("record_video_frame: malloc failed"); | ||
| 595 | + free(frData.data0); | ||
| 596 | + free(frData.data1); | ||
| 597 | + free(frData.data2); | ||
| 598 | + return; | ||
| 599 | + } | ||
| 593 | frData.dataNum = FRAME_DATA_NUM_3; | 600 | frData.dataNum = FRAME_DATA_NUM_3; |
| 594 | frData.frameType = OHOS_FRAME_TYPE_VIDEO; | 601 | frData.frameType = OHOS_FRAME_TYPE_VIDEO; |
| 595 | frData.lineSize0 = frame->linesize[DATA_NUM_0]; | 602 | frData.lineSize0 = frame->linesize[DATA_NUM_0]; |
| @@ -606,8 +613,6 @@ static void record_video_frame(FFPlayer *ffp, AVFrame *frame) | |||
| 606 | int windex = ffp->record_write_data.windex; | 613 | int windex = ffp->record_write_data.windex; |
| 607 | ffp->record_write_data.recordFramesQueue[windex] = frData; | 614 | ffp->record_write_data.recordFramesQueue[windex] = frData; |
| 608 | ffp->record_write_data.windex += DATA_NUM_1; | 615 | ffp->record_write_data.windex += DATA_NUM_1; |
| 609 | - ffp->record_write_data.srcFormat.height = frame->height; | ||
| 610 | - ffp->record_write_data.srcFormat.width = frame->width; | ||
| 611 | } | 616 | } |
| 612 | } | 617 | } |
| 613 | 618 | ||
| @@ -5235,15 +5240,42 @@ int ffp_start_record(FFPlayer *ffp, const char *recordFilePath) | |||
| 5235 | if (!ffp) { | 5240 | if (!ffp) { |
| 5236 | return OHOS_RECORD_CALLBACK_STATUS_FAILED; | 5241 | return OHOS_RECORD_CALLBACK_STATUS_FAILED; |
| 5237 | } | 5242 | } |
| 5243 | + if (ffp->record_write_data.recThreadid) { | ||
【AI-Review】【一般】【基础代码问题】【资源使用问题】ffp_start_record 停止旧录制时未释放旧 recordFilePath 导致内存泄漏 ● 问题:当 ● 影响:一般。频繁调用 ● 建议:在
![]() ![]() 【AI-Review】【严重】【基础代码问题】【代码逻辑错误】ffp_start_record 中 recThreadid 非零时未释放 recordFilePath 和 recordFramesQueue ● 问题: ● 影响:严重。每次重复启动录制都会泄漏 ● 建议:在
![]() ![]() | |||
| 5244 | + ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF; | ||
| 5245 | + UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF); | ||
| 5246 | + pthread_join(ffp->record_write_data.recThreadid, NULL); | ||
| 5247 | + ffp->record_write_data.recThreadid = 0; | ||
| 5248 | + } | ||
| 5238 | ffp->record_write_data.windex = 0; | 5249 | ffp->record_write_data.windex = 0; |
| 5239 | ffp->record_write_data.rindex = 0; | 5250 | ffp->record_write_data.rindex = 0; |
| 5240 | - ffp->record_write_data.recordFramesQueue = (RecordFrameData*)malloc(OHOS_MAX_DECODE_FRAME_SIZE * sizeof(int)); | 5251 | + ffp->record_write_data.srcFormat.width = 0; |
| 5252 | + ffp->record_write_data.srcFormat.height = 0; | ||
| 5253 | + ffp->record_write_data.recordFramesQueue = | ||
| 5254 | + (RecordFrameData *)malloc((size_t)OHOS_MAX_DECODE_FRAME_SIZE * sizeof(RecordFrameData)); | ||
| 5255 | + if (!ffp->record_write_data.recordFramesQueue) { | ||
| 5256 | + LOGE("recordFramesQueue malloc failed"); | ||
| 5257 | + return OHOS_RECORD_CALLBACK_STATUS_FAILED; | ||
| 5258 | + } | ||
| 5259 | + memset(ffp->record_write_data.recordFramesQueue, 0, (size_t)OHOS_MAX_DECODE_FRAME_SIZE * sizeof(RecordFrameData)); | ||
| 5260 | + if (ffp->record_write_data.recordFilePath) { | ||
| 5261 | + free(ffp->record_write_data.recordFilePath); | ||
| 5262 | + ffp->record_write_data.recordFilePath = NULL; | ||
| 5263 | + } | ||
| 5241 | char* filePath = (char*)malloc(strlen(recordFilePath) + 1); | 5264 | char* filePath = (char*)malloc(strlen(recordFilePath) + 1); |
【AI-Review】【严重】【基础代码问题】【代码逻辑错误】filePath malloc 返回值未检查导致空指针崩溃 ● 问题: ● 影响:严重。当内存不足时 malloc 返回 NULL,后续 memcpy 会导致空指针写入崩溃(SIGSEGV),录制功能不可用且可能影响整个播放器进程稳定性。 ● 建议:添加 NULL 检查,与 recordFramesQueue 的处理方式保持一致:
![]() ![]() | |||
| 5242 | memcpy(filePath, recordFilePath, strlen(recordFilePath) + 1); | 5265 | memcpy(filePath, recordFilePath, strlen(recordFilePath) + 1); |
| 5243 | ffp->record_write_data.recordFilePath = filePath; | 5266 | ffp->record_write_data.recordFilePath = filePath; |
| 5244 | ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_ON; | 5267 | ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_ON; |
| 5245 | UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_ON); | 5268 | UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_ON); |
| 5246 | - pthread_create(&(ffp->record_write_data.recThreadid), NULL, WriteRecordFile, (void *)(ffp)); | 5269 | + if (pthread_create(&(ffp->record_write_data.recThreadid), NULL, WriteRecordFile, (void *)(ffp)) != 0) { |
| 5270 | + LOGE("pthread_create WriteRecordFile failed"); | ||
| 5271 | + free(ffp->record_write_data.recordFilePath); | ||
| 5272 | + ffp->record_write_data.recordFilePath = NULL; | ||
| 5273 | + free(ffp->record_write_data.recordFramesQueue); | ||
| 5274 | + ffp->record_write_data.recordFramesQueue = NULL; | ||
| 5275 | + ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF; | ||
| 5276 | + UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF); | ||
| 5277 | + return OHOS_RECORD_CALLBACK_STATUS_FAILED; | ||
| 5278 | + } | ||
| 5247 | return OHOS_RECORD_CALLBACK_STATUS_SUCCESS; | 5279 | return OHOS_RECORD_CALLBACK_STATUS_SUCCESS; |
| 5248 | } | 5280 | } |
| 5249 | 5281 | ||
| @@ -5254,6 +5286,10 @@ int ffp_stop_record(FFPlayer *ffp) | |||
| 5254 | } | 5286 | } |
| 5255 | ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF; | 5287 | ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF; |
| 5256 | UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF); | 5288 | UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF); |
| 5289 | + if (ffp->record_write_data.recThreadid) { | ||
【AI-Review】【一般】【基础代码问题】【稳定性问题】ffp_stop_record 中 pthread_join 可能长时间阻塞调用线程 ● 问题: ● 影响:一般。停止录制时可能导致 UI 线程阻塞,用户体验差。 ● 建议:考虑将
![]() ![]() | |||
| 5290 | + pthread_join(ffp->record_write_data.recThreadid, NULL); | ||
| 5291 | + ffp->record_write_data.recThreadid = 0; | ||
| 5292 | + } | ||
| 5257 | return FindRecordResult(ffp); | 5293 | return FindRecordResult(ffp); |
| 5258 | } | 5294 | } |
| 5259 | 5295 | ||
| @@ -947,9 +947,6 @@ void RecordMediaCodecVideoFrame(FFPlayer *ffp, AVFrame *frame) | |||
| 947 | ffp->record_write_data.recordFramesQueue[writeIndex] = frData; | 947 | ffp->record_write_data.recordFramesQueue[writeIndex] = frData; |
| 948 | ffp->record_write_data.windex += DATA_NUM_1; | 948 | ffp->record_write_data.windex += DATA_NUM_1; |
| 949 | 949 | ||
| 950 | - ffp->record_write_data.srcFormat.height = yuv420pFrame->height; | ||
| 951 | - ffp->record_write_data.srcFormat.width = yuv420pFrame->width; | ||
| 952 | - | ||
| 953 | av_freep(yuv420pFrame->data); | 950 | av_freep(yuv420pFrame->data); |
| 954 | av_frame_free(&yuv420pFrame); | 951 | av_frame_free(&yuv420pFrame); |
| 955 | } | 952 | } |
| @@ -54,9 +54,14 @@ int CalculateFrameRate(FFPlayer *ffp) | |||
| 54 | if (IS_PRIORITY) { | 54 | if (IS_PRIORITY) { |
| 55 | return DEFAULT_FRAME_RATE; | 55 | return DEFAULT_FRAME_RATE; |
| 56 | } | 56 | } |
| 57 | + //空指针保护:video_st 可能为 NULL | ||
| 58 | + if (!ffp || !ffp->is || !ffp->is->video_st) { | ||
| 59 | + LOGE("CalculateFrameRate: ffp/is/video_st is NULL, return default frame rate"); | ||
| 60 | + return DEFAULT_FRAME_RATE; | ||
| 61 | + } | ||
| 57 | RecordWriteData *recordWriteData = &ffp->record_write_data; | 62 | RecordWriteData *recordWriteData = &ffp->record_write_data; |
| 58 | //采集到的视频帧数量大于等于5 | 63 | //采集到的视频帧数量大于等于5 |
| 59 | - if (recordWriteData->windex >= DATA_NUM_5) { | 64 | + if (recordWriteData->recordFramesQueue && recordWriteData->windex >= DATA_NUM_5) { |
| 60 | //计算相邻pts变化值 | 65 | //计算相邻pts变化值 |
| 61 | int64_t diffPTS1 = recordWriteData->recordFramesQueue[2].pts - recordWriteData->recordFramesQueue[1].pts; | 66 | int64_t diffPTS1 = recordWriteData->recordFramesQueue[2].pts - recordWriteData->recordFramesQueue[1].pts; |
| 62 | int64_t diffPTS2 = recordWriteData->recordFramesQueue[4].pts - recordWriteData->recordFramesQueue[3].pts; | 67 | int64_t diffPTS2 = recordWriteData->recordFramesQueue[4].pts - recordWriteData->recordFramesQueue[3].pts; |
| @@ -73,6 +78,28 @@ int CalculateFrameRate(FFPlayer *ffp) | |||
| 73 | return DEFAULT_FRAME_RATE; | 78 | return DEFAULT_FRAME_RATE; |
| 74 | } | 79 | } |
| 75 | 80 | ||
| 81 | +//安全获取录制帧率:对 video_st 等指针做空指针校验,避免崩溃 | ||
| 82 | +static int GetSafeRecordFrameRate(FFPlayer *ffp) | ||
| 83 | +{ | ||
| 84 | + //如果优先走默认帧率 | ||
| 85 | + if (IS_PRIORITY) { | ||
| 86 | + return DEFAULT_FRAME_RATE; | ||
| 87 | + } | ||
| 88 | + //安全校验指针链后再调用 av_guess_frame_rate | ||
| 89 | + if (ffp && ffp->is && ffp->is->ic && ffp->is->video_st) { | ||
| 90 | + AVRational fr = av_guess_frame_rate(ffp->is->ic, ffp->is->video_st, NULL); | ||
| 91 | + if (fr.num > 0 && fr.den > 0) { | ||
| 92 | + int fps = (int)(av_q2d(fr) + 0.5); | ||
| 93 | + if (fps > 0) { | ||
| 94 | + return fps; | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + //av_guess_frame_rate 失败或 video_st 为空,回退到 pts 计算帧率 | ||
| 99 | + int fps = CalculateFrameRate(ffp); | ||
| 100 | + return fps > 0 ? fps : DEFAULT_FRAME_RATE; | ||
| 101 | +} | ||
| 102 | + | ||
| 76 | void handleCodecAudioStream(AVCodecContext *c, OutputStream *ost, AVCodec **codec, int sample_rate) | 103 | void handleCodecAudioStream(AVCodecContext *c, OutputStream *ost, AVCodec **codec, int sample_rate) |
| 77 | { | 104 | { |
| 78 | int i; | 105 | int i; |
| @@ -162,10 +189,11 @@ int OpenVideo(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictiona | |||
| 162 | AVCodecContext *c = ost->st->codec; | 189 | AVCodecContext *c = ost->st->codec; |
| 163 | AVDictionary *opt = NULL; | 190 | AVDictionary *opt = NULL; |
| 164 | av_dict_copy(&opt, opt_arg, 0); | 191 | av_dict_copy(&opt, opt_arg, 0); |
| 165 | - int frameRate = av_q2d(av_guess_frame_rate(ffp->is->ic, ffp->is->video_st, NULL)); | 192 | + int frameRate = GetSafeRecordFrameRate(ffp); |
| 166 | //如果帧率等于0或时间基异常场景走ffp拿到的time_base,若ffp拿到time_base异常走pts计算帧率 | 193 | //如果帧率等于0或时间基异常场景走ffp拿到的time_base,若ffp拿到time_base异常走pts计算帧率 |
| 167 | if (frameRate <= 0 || (c->time_base.num <= 0) || (c->time_base.den <= 0)) { | 194 | if (frameRate <= 0 || (c->time_base.num <= 0) || (c->time_base.den <= 0)) { |
| 168 | - if (!IS_PRIORITY && (ffp->is->video_st->time_base.num > 0) && (ffp->is->video_st->time_base.den > 0)) { | 195 | + if (!IS_PRIORITY && ffp->is && ffp->is->video_st && |
| 196 | + (ffp->is->video_st->time_base.num > 0) && (ffp->is->video_st->time_base.den > 0)) { | ||
| 169 | c->time_base = ffp->is->video_st->time_base; | 197 | c->time_base = ffp->is->video_st->time_base; |
| 170 | } else { | 198 | } else { |
| 171 | c->time_base = (AVRational){1, CalculateFrameRate(ffp)}; | 199 | c->time_base = (AVRational){1, CalculateFrameRate(ffp)}; |
| @@ -384,6 +412,78 @@ int WriteAudioFrame(AVFormatContext *oc, OutputStream *ost, AVFrame *curFr) | |||
| 384 | return (frame || gotPacket) ? OHOS_RECORD_CALLBACK_STATUS_SUCCESS : OHOS_RECORD_CALLBACK_STATUS_FAILED; | 412 | return (frame || gotPacket) ? OHOS_RECORD_CALLBACK_STATUS_SUCCESS : OHOS_RECORD_CALLBACK_STATUS_FAILED; |
| 385 | } | 413 | } |
| 386 | 414 | ||
| 415 | +static void FlushVideoEncoder(AVFormatContext *oc, OutputStream *ost) | ||
【AI-Review】【建议】【软件设计】【冗余重复代码】FlushVideoEncoder 和 FlushAudioEncoder 代码高度重复 ● 问题: ● 影响:建议。功能正确,但增加了维护成本——修改一处逻辑时容易遗漏另一处。 ● 建议:可提取公共模板函数,通过函数指针或编码类型参数区分:
![]() ![]() 【AI-Review】【一般】【基础代码问题】【性能和效率问题】FlushVideoEncoder/FlushAudioEncoder 使用已废弃的 FFmpeg API ● 问题:新增的 ● 影响:一般。使用废弃 API 不会立即导致功能问题,但会增加未来 FFmpeg 版本升级的迁移成本,且旧 API 可能在未来版本中被移除。 ● 建议:使用推荐的
![]() ![]() | |||
| 416 | +{ | ||
| 417 | + AVCodecContext *c; | ||
| 418 | + int ret; | ||
| 419 | + | ||
| 420 | + if (!oc || !ost || !ost->st) { | ||
| 421 | + return; | ||
| 422 | + } | ||
| 423 | + c = ost->st->codec; | ||
| 424 | + if (!c) { | ||
| 425 | + return; | ||
| 426 | + } | ||
| 427 | + while (true) { | ||
| 428 | + AVPacket pkt = {0}; | ||
| 429 | + int gotPacket = 0; | ||
| 430 | + | ||
| 431 | + av_init_packet(&pkt); | ||
| 432 | + ret = avcodec_encode_video2(c, &pkt, NULL, &gotPacket); | ||
| 433 | + if (ret < 0) { | ||
| 434 | + LOGE("Flush video encoder failed: %s", av_err2str(ret)); | ||
| 435 | + av_packet_unref(&pkt); | ||
| 436 | + break; | ||
| 437 | + } | ||
| 438 | + if (!gotPacket) { | ||
| 439 | + av_packet_unref(&pkt); | ||
| 440 | + break; | ||
| 441 | + } | ||
| 442 | + ret = WriteFrame(oc, &c->time_base, ost->st, &pkt); | ||
| 443 | + av_packet_unref(&pkt); | ||
| 444 | + if (ret < 0) { | ||
| 445 | + LOGE("Flush video encoder write failed: %s", av_err2str(ret)); | ||
| 446 | + break; | ||
| 447 | + } | ||
| 448 | + } | ||
| 449 | +} | ||
| 450 | + | ||
| 451 | +static void FlushAudioEncoder(AVFormatContext *oc, OutputStream *ost) | ||
| 452 | +{ | ||
| 453 | + AVCodecContext *c; | ||
| 454 | + int ret; | ||
| 455 | + | ||
| 456 | + if (!oc || !ost || !ost->st) { | ||
| 457 | + return; | ||
| 458 | + } | ||
| 459 | + c = ost->st->codec; | ||
| 460 | + if (!c) { | ||
| 461 | + return; | ||
| 462 | + } | ||
| 463 | + while (true) { | ||
| 464 | + AVPacket pkt = {0}; | ||
| 465 | + int gotPacket = 0; | ||
| 466 | + | ||
| 467 | + av_init_packet(&pkt); | ||
| 468 | + ret = avcodec_encode_audio2(c, &pkt, NULL, &gotPacket); | ||
| 469 | + if (ret < 0) { | ||
| 470 | + LOGE("Flush audio encoder failed: %s", av_err2str(ret)); | ||
| 471 | + av_packet_unref(&pkt); | ||
| 472 | + break; | ||
| 473 | + } | ||
| 474 | + if (!gotPacket) { | ||
| 475 | + av_packet_unref(&pkt); | ||
| 476 | + break; | ||
| 477 | + } | ||
| 478 | + ret = WriteFrame(oc, &c->time_base, ost->st, &pkt); | ||
| 479 | + av_packet_unref(&pkt); | ||
| 480 | + if (ret < 0) { | ||
| 481 | + LOGE("Flush audio encoder write failed: %s", av_err2str(ret)); | ||
| 482 | + break; | ||
| 483 | + } | ||
| 484 | + } | ||
| 485 | +} | ||
| 486 | + | ||
| 387 | void CloseStream(AVFormatContext *oc, OutputStream *ost) | 487 | void CloseStream(AVFormatContext *oc, OutputStream *ost) |
| 388 | { | 488 | { |
| 389 | avcodec_close(ost->st->codec); | 489 | avcodec_close(ost->st->codec); |
| @@ -412,7 +512,7 @@ VideoAudioAvCodec VideoAudioStreamAndAvcodecOpen(RecordWriteData *recordWriteDat | |||
| 412 | int haveVideo = 0, haveAudio = 0; | 512 | int haveVideo = 0, haveAudio = 0; |
| 413 | VideoAudioAvCodec vaAvcodec; | 513 | VideoAudioAvCodec vaAvcodec; |
| 414 | AVOutputFormat *fmt = recordWriteData->oc->oformat; | 514 | AVOutputFormat *fmt = recordWriteData->oc->oformat; |
| 415 | - int frameRate = av_q2d(av_guess_frame_rate(mFFPlayer->is->ic, mFFPlayer->is->video_st, NULL)); | 515 | + int frameRate = GetSafeRecordFrameRate(mFFPlayer); |
| 416 | if (fmt->video_codec != AV_CODEC_ID_NONE) { | 516 | if (fmt->video_codec != AV_CODEC_ID_NONE) { |
| 417 | int addStreamResult = | 517 | int addStreamResult = |
| 418 | AddStream(&(recordWriteData->video_st), recordWriteData->oc, &(recordWriteData->video_codec), | 518 | AddStream(&(recordWriteData->video_st), recordWriteData->oc, &(recordWriteData->video_codec), |
| @@ -462,6 +562,19 @@ void WriteVideoFrameData(AVFormatContext *oc, RecordFrameData frData, SwsContext | |||
| 462 | LOGE("ERROR: WriteVideoFrameData Invalid parameter!\n"); | 562 | LOGE("ERROR: WriteVideoFrameData Invalid parameter!\n"); |
| 463 | return; | 563 | return; |
| 464 | } | 564 | } |
| 565 | + if (frData.width <= 0 || frData.height <= 0) { | ||
| 566 | + LOGE("ERROR: WriteVideoFrameData invalid frame size %dx%d\n", frData.width, frData.height); | ||
| 567 | + return; | ||
| 568 | + } | ||
| 569 | + if (!frData.data0 || !frData.data1 || (frData.dataNum == FRAME_DATA_NUM_3 && !frData.data2)) { | ||
| 570 | + LOGE("ERROR: WriteVideoFrameData invalid frame buffer\n"); | ||
| 571 | + return; | ||
| 572 | + } | ||
| 573 | + AVFrame *dstFrame = recordWriteData->video_st.frame; | ||
| 574 | + if (!dstFrame || !dstFrame->data[DATA_NUM_0]) { | ||
| 575 | + LOGE("ERROR: WriteVideoFrameData encoder frame is NULL\n"); | ||
| 576 | + return; | ||
| 577 | + } | ||
| 465 | 578 | ||
| 466 | LOGI("WriteVideoFrameData start\n"); | 579 | LOGI("WriteVideoFrameData start\n"); |
| 467 | uint8_t *srcSlice[3]; | 580 | uint8_t *srcSlice[3]; |
| @@ -472,8 +585,12 @@ void WriteVideoFrameData(AVFormatContext *oc, RecordFrameData frData, SwsContext | |||
| 472 | srcLineSize[DATA_NUM_0] = frData.lineSize0; | 585 | srcLineSize[DATA_NUM_0] = frData.lineSize0; |
| 473 | srcLineSize[DATA_NUM_1] = frData.lineSize1; | 586 | srcLineSize[DATA_NUM_1] = frData.lineSize1; |
| 474 | srcLineSize[DATA_NUM_2] = frData.lineSize2; | 587 | srcLineSize[DATA_NUM_2] = frData.lineSize2; |
| 475 | - sws_scale(swsContext, static_cast<const uint8_t *const *>(srcSlice), srcLineSize, 0, frData.height, | 588 | + int scaled = sws_scale(swsContext, static_cast<const uint8_t *const *>(srcSlice), srcLineSize, 0, frData.height, |
| 476 | - recordWriteData->video_st.frame->data, recordWriteData->video_st.frame->linesize); | 589 | + dstFrame->data, dstFrame->linesize); |
| 590 | + if (scaled <= 0) { | ||
| 591 | + LOGE("ERROR: WriteVideoFrameData sws_scale failed, ret=%d\n", scaled); | ||
| 592 | + return; | ||
| 593 | + } | ||
| 477 | //如果优先走默认帧率或获取的帧率大于0时走next_pts++,否则走解码获取的pts | 594 | //如果优先走默认帧率或获取的帧率大于0时走next_pts++,否则走解码获取的pts |
| 478 | if (IS_PRIORITY || (frameRate > 0)) { | 595 | if (IS_PRIORITY || (frameRate > 0)) { |
| 479 | recordWriteData->video_st.frame->pts = recordWriteData->video_st.next_pts++; | 596 | recordWriteData->video_st.frame->pts = recordWriteData->video_st.next_pts++; |
| @@ -583,8 +700,10 @@ void StartEncoderWrite(AVFormatContext *oc, RecordWriteData *recordWriteData, FF | |||
| 583 | OutputStream *audioStPtr, int frameRate) | 700 | OutputStream *audioStPtr, int frameRate) |
| 584 | { | 701 | { |
| 585 | RecordVideoSwsState swsState; | 702 | RecordVideoSwsState swsState; |
【AI-Review】【一般】【基础代码问题】【稳定性问题】StartEncoderWrite 中 swsState 未显式初始化 ● 问题: ● 影响: 一般。如果编译器未正确零初始化,首次 sws_getContext 调用可能使用错误的 outWidth/outHeight(0x0),导致 sws_scale 失败。 ● 建议: 显式初始化 swsState:
或者使用 ![]() ![]() | |||
| 586 | - swsState.outWidth = recordWriteData->srcFormat.width; | 703 | + AVCodecContext *videoCodec = recordWriteData->video_st.st ? recordWriteData->video_st.st->codec : NULL; |
| 587 | - swsState.outHeight = recordWriteData->srcFormat.height; | 704 | + |
| 705 | + swsState.outWidth = videoCodec ? videoCodec->width : recordWriteData->srcFormat.width; | ||
【AI-Review】【严重】【基础代码问题】【代码逻辑错误】srcFormat 为零时 StartEncoderWrite 的 fallback 路径使用零分辨率 ● 问题: PR 删除了 ● 影响: 严重。纯音频流或首帧非视频帧的场景下,录制输出文件可能损坏或录制失败。 ● 建议: 在
![]() ![]() | |||
| 706 | + swsState.outHeight = videoCodec ? videoCodec->height : recordWriteData->srcFormat.height; | ||
| 588 | while (true) { | 707 | while (true) { |
| 589 | recordWriteData = &mFFPlayer->record_write_data; | 708 | recordWriteData = &mFFPlayer->record_write_data; |
| 590 | int totalFrameIndex = recordWriteData->windex; | 709 | int totalFrameIndex = recordWriteData->windex; |
| @@ -608,18 +727,24 @@ void StartEncoderWrite(AVFormatContext *oc, RecordWriteData *recordWriteData, FF | |||
| 608 | void ReleaseResources(AVFormatContext *oc, RecordWriteData *recordWriteData, AVOutputFormat *fmt, | 727 | void ReleaseResources(AVFormatContext *oc, RecordWriteData *recordWriteData, AVOutputFormat *fmt, |
| 609 | int haveVideo, int haveAudio, OutputStream *videoStPtr, OutputStream *audioStPtr) | 728 | int haveVideo, int haveAudio, OutputStream *videoStPtr, OutputStream *audioStPtr) |
| 610 | { | 729 | { |
| 611 | - FreeRecordFrames(recordWriteData); | 730 | + if (haveVideo) { |
| 612 | - av_write_trailer(oc); | 731 | + FlushVideoEncoder(oc, videoStPtr); |
| 613 | - if (haveVideo) { | 732 | + } |
| 733 | + if (haveAudio) { | ||
| 734 | + FlushAudioEncoder(oc, audioStPtr); | ||
| 735 | + } | ||
| 736 | + FreeRecordFrames(recordWriteData); | ||
| 737 | + av_write_trailer(oc); | ||
| 738 | + if (haveVideo) { | ||
| 614 | CloseStream(oc, videoStPtr); | 739 | CloseStream(oc, videoStPtr); |
| 615 | - } | 740 | + } |
| 616 | - if (haveAudio) { | 741 | + if (haveAudio) { |
| 617 | CloseStream(oc, audioStPtr); | 742 | CloseStream(oc, audioStPtr); |
| 618 | - } | 743 | + } |
| 619 | - if (!(fmt->flags & AVFMT_NOFILE)) { | 744 | + if (fmt && !(fmt->flags & AVFMT_NOFILE)) { |
| 620 | avio_closep(&oc->pb); | 745 | avio_closep(&oc->pb); |
| 621 | - } | 746 | + } |
| 622 | - avformat_free_context(oc); | 747 | + avformat_free_context(oc); |
| 623 | } | 748 | } |
| 624 | 749 | ||
| 625 | int CheckAVFormatContext(AVFormatContext *oc, FFPlayer *mFFPlayer, const char *fileName) | 750 | int CheckAVFormatContext(AVFormatContext *oc, FFPlayer *mFFPlayer, const char *fileName) |
| @@ -651,7 +776,8 @@ RecordWriteData* WaitCacheVideoFrames(FFPlayer *mFFPlayer) | |||
| 651 | int WriteHeader(FFPlayer *mFFPlayer, AVDictionary *opt, int frameRate) | 776 | int WriteHeader(FFPlayer *mFFPlayer, AVDictionary *opt, int frameRate) |
| 652 | { | 777 | { |
| 653 | LOGE("WriteHeader frameRate %d time_base.num %d time_base.den %d", frameRate, | 778 | LOGE("WriteHeader frameRate %d time_base.num %d time_base.den %d", frameRate, |
| 654 | - mFFPlayer->is->video_st->time_base.num, mFFPlayer->is->video_st->time_base.den); | 779 | + (mFFPlayer->is && mFFPlayer->is->video_st) ? mFFPlayer->is->video_st->time_base.num : 0, |
| 780 | + (mFFPlayer->is && mFFPlayer->is->video_st) ? mFFPlayer->is->video_st->time_base.den : 0); | ||
| 655 | if (frameRate <= 0) { | 781 | if (frameRate <= 0) { |
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】WriteHeader 仍直接调用 CalculateFrameRate 可能返回负帧率 ● 问题: ● 影响:一般。在 PTS 乱序的异常视频流中,可能向编码器设置负 timescale 值,导致录制输出文件损坏。 ● 建议:统一使用
![]() ![]() | |||
| 656 | av_dict_set_int(&opt, "video_track_timescale", CalculateFrameRate(mFFPlayer), 0); | 782 | av_dict_set_int(&opt, "video_track_timescale", CalculateFrameRate(mFFPlayer), 0); |
| 657 | } | 783 | } |
| @@ -713,7 +839,7 @@ int WriteRecordFile(void *recordData) | |||
| 713 | goto fail; | 839 | goto fail; |
| 714 | } | 840 | } |
| 715 | } | 841 | } |
| 716 | - frameRate = av_q2d(av_guess_frame_rate(mFFPlayer->is->ic, mFFPlayer->is->video_st, NULL)); | 842 | + frameRate = GetSafeRecordFrameRate(mFFPlayer); |
| 717 | ret = WriteHeader(mFFPlayer, opt, frameRate); | 843 | ret = WriteHeader(mFFPlayer, opt, frameRate); |
| 718 | if (ret < 0) { | 844 | if (ret < 0) { |
| 719 | LOGE("Error occurred when opening output file %s", av_err2str(ret)); | 845 | LOGE("Error occurred when opening output file %s", av_err2str(ret)); |
| @@ -757,9 +883,13 @@ int FindRecordResult(void *ffp) | |||
| 757 | } | 883 | } |
| 758 | 884 | ||
| 759 | std::unique_lock<std::mutex> lock(ijkplayerRecordResultMutex); | 885 | std::unique_lock<std::mutex> lock(ijkplayerRecordResultMutex); |
| 886 | + auto it = ijkplayerRecordResultMap.find(ffp); | ||
| 887 | + if (it != ijkplayerRecordResultMap.end()) { | ||
| 888 | + return it->second; | ||
| 889 | + } | ||
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】FindRecordResult 先 find 再 wait_for 存在通知丢失风险 ● 问题:PR 修改了 ● 影响:一般。逻辑冗余,不会导致功能错误,但增加了代码理解的复杂度。如果未来移除 ● 建议:如果
![]() ![]() | |||
| 760 | if (ijkplayerRecordResultCondition.wait_for(lock, | 890 | if (ijkplayerRecordResultCondition.wait_for(lock, |
| 761 | std::chrono::milliseconds(RECORD_RESULT_TIMEOUT_MS)) == std::cv_status::no_timeout) { | 891 | std::chrono::milliseconds(RECORD_RESULT_TIMEOUT_MS)) == std::cv_status::no_timeout) { |
| 762 | - auto it = ijkplayerRecordResultMap.find(ffp); | 892 | + it = ijkplayerRecordResultMap.find(ffp); |
| 763 | if (it != ijkplayerRecordResultMap.end()) { | 893 | if (it != ijkplayerRecordResultMap.end()) { |
| 764 | return it->second; | 894 | return it->second; |
| 765 | } | 895 | } |


🤖 AI 代码检视意见 | 🔒 Security | 🚨 Critical | 行号区间: L590-L599
问题描述: 在 record_video_frame 函数中,若 data1 的 malloc 失败,data2 尚未被赋值,此时 frData.data2 仍为栈上的随机未初始化值。随后执行的 free(frData.data2) 会对该野指针进行释放,导致未定义行为、堆损坏或直接崩溃。
💡 查看修复建议(点击展开)
修改建议:在 malloc 之前将 frData.data0, frData.data1, frData.data2 显式初始化为 NULL,确保失败路径下的 free 操作是安全的。