Merged
bugfix 分辨率切换时录制概率崩溃 #231
bugfix 分辨率切换时录制概率崩溃 #231
Merged
linsanjiuAdd on Jun 18
Total: 3 files changed+190-27
@@ -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+ }
linsanjiu
linsanjiulinsanjiuJun 26

🤖 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 操作是安全的。

frData.data0 = NULL;
frData.data1 = NULL;
frData.data2 = NULL;
frData.data0 = (uint8_t *)malloc((size_t)frame->linesize[0] * frame->height);
frData.data1 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2);
frData.data2 = (uint8_t *)malloc((size_t)frame->linesize[1] * frame->height / DATA_NUM_2);
if (!frData.data0 || !frData.data1 || !frData.data2) {
LOGE("record_video_frame: malloc failed");
free(frData.data0);
free(frData.data1);
free(frData.data2);
return;
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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) {
cpf-managercpf-manager

【AI-Review】【一般】【基础代码问题】【资源使用问题】ffp_start_record 停止旧录制时未释放旧 recordFilePath 导致内存泄漏

● 问题:当 ffp->record_write_data.recThreadid 非零时,代码会停止旧录制线程然后启动新录制。但在 pthread_join 之后、分配新 filePath 之前,未释放旧的 ffp->record_write_data.recordFilePath。旧录制线程的 WriteRecordFile 在 fail 路径和正常路径中均不会释放 recordFilePath(FreeRecordFrames 只释放 recordFramesQueue),因此每次重新开始录制都会泄漏上一次的 recordFilePath。

● 影响:一般。频繁调用 ffp_start_record(先停止再重新录制)会累积内存泄漏。每次泄漏大小为录制文件路径字符串长度+1。

● 建议:在 pthread_join 之后、分配新 filePath 之前,释放旧的 recordFilePath:

if (ffp->record_write_data.recThreadid) {
    ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF;
    UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF);
    pthread_join(ffp->record_write_data.recThreadid, NULL);
    ffp->record_write_data.recThreadid = 0;
    free(ffp->record_write_data.recordFilePath);
    ffp->record_write_data.recordFilePath = NULL;
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】ffp_start_record 中 recThreadid 非零时未释放 recordFilePath 和 recordFramesQueue

● 问题:ffp_start_record 中当 ffp->record_write_data.recThreadid 非零时,会先 pthread_join 等待旧线程结束,但 pthread_join 之后旧的 recordFilePath 和 recordFramesQueue 没有被释放就立即分配新的。WriteRecordFile 线程中 FreeRecordFrames 会释放 recordFramesQueue,但 recordFilePath 只在 fail 路径中被释放。如果录制正常完成(非 fail 路径),recordFilePath 由 ReleaseResources 释放——但 ReleaseResources 只释放 oc 相关资源,不释放 recordFilePath。因此每次重复调用 ffp_start_record 都会泄漏上一次的 recordFilePath。

● 影响:严重。每次重复启动录制都会泄漏 recordFilePath 的内存。在频繁开始/停止录制的场景下,内存泄漏会持续累积。

● 建议:在 pthread_join 之后释放旧的 recordFilePath:

if (ffp->record_write_data.recThreadid) {
    ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF;
    UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF);
    pthread_join(ffp->record_write_data.recThreadid, NULL);
    ffp->record_write_data.recThreadid = 0;
    // 释放旧的录制资源
    if (ffp->record_write_data.recordFilePath) {
        free(ffp->record_write_data.recordFilePath);
        ffp->record_write_data.recordFilePath = NULL;
    }
    if (ffp->record_write_data.recordFramesQueue) {
        free(ffp->record_write_data.recordFramesQueue);
        ffp->record_write_data.recordFramesQueue = NULL;
    }
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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);
cpf-manager

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】filePath malloc 返回值未检查导致空指针崩溃

● 问题:char* filePath = (char*)malloc(strlen(recordFilePath) + 1) 的返回值未做 NULL 检查,紧接着 memcpy(filePath, recordFilePath, strlen(recordFilePath) + 1) 会对 NULL 指针写入,导致程序崩溃。本 PR 对 recordFramesQueue 的 malloc 已添加了 NULL 检查,但对 filePath 的 malloc 遗漏了。

● 影响:严重。当内存不足时 malloc 返回 NULL,后续 memcpy 会导致空指针写入崩溃(SIGSEGV),录制功能不可用且可能影响整个播放器进程稳定性。

● 建议:添加 NULL 检查,与 recordFramesQueue 的处理方式保持一致:

char* filePath = (char*)malloc(strlen(recordFilePath) + 1);
if (!filePath) {
    LOGE("filePath malloc failed");
    free(ffp->record_write_data.recordFramesQueue);
    ffp->record_write_data.recordFramesQueue = NULL;
    return OHOS_RECORD_CALLBACK_STATUS_FAILED;
}
memcpy(filePath, recordFilePath, strlen(recordFilePath) + 1);
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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) {
cpf-manager

【AI-Review】【一般】【基础代码问题】【稳定性问题】ffp_stop_record 中 pthread_join 可能长时间阻塞调用线程

● 问题:ffp_stop_record 中新增了 pthread_join(ffp->record_write_data.recThreadid, NULL) 等待 WriteRecordFile 线程完成。但 WriteRecordFile 中的 StartEncoderWrite 循环需要编码并写入所有缓存的帧,在视频较长或编码速度慢时可能非常耗时。如果 ffp_stop_record 从 UI 线程调用,会导致界面卡顿甚至 ANR。

● 影响:一般。停止录制时可能导致 UI 线程阻塞,用户体验差。

● 建议:考虑将 pthread_join 改为异步等待机制,或设置超时。例如在 WriteRecordFile 线程完成后再回调通知结果(已有 FindRecordResult 的条件变量机制),避免在 ffp_stop_record 中同步等待:

int ffp_stop_record(FFPlayer *ffp)
{
    if (!ffp) {
        return OHOS_RECORD_STATUS_OFF;
    }
    ffp->record_write_data.isInRecord = OHOS_RECORD_STATUS_OFF;
    UpdateRecordStatus(ffp, OHOS_RECORD_STATUS_OFF);
    // 不在此处 pthread_join,让 WriteRecordFile 线程自行退出后通过 FindRecordResult 异步获取结果
    return FindRecordResult(ffp);
}

FindRecordResult 已有条件变量等待机制,可以设置合理的超时时间。

likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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 //采集到的视频帧数量大于等于563 //采集到的视频帧数量大于等于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+ 
76void handleCodecAudioStream(AVCodecContext *c, OutputStream *ost, AVCodec **codec, int sample_rate)103void 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)
cpf-managercpf-manager

【AI-Review】【建议】【软件设计】【冗余重复代码】FlushVideoEncoder 和 FlushAudioEncoder 代码高度重复

● 问题:FlushVideoEncoder 和 FlushAudioEncoder 的逻辑几乎完全相同,仅编码函数不同(avcodec_encode_video2 vs avcodec_encode_audio2)和日志信息不同。两个函数共约 60 行代码,重复度超过 90%。

● 影响:建议。功能正确,但增加了维护成本——修改一处逻辑时容易遗漏另一处。

● 建议:可提取公共模板函数,通过函数指针或编码类型参数区分:

static void FlushEncoder(AVFormatContext *oc, OutputStream *ost,
                         int (*encodeFunc)(AVCodecContext*, AVPacket*, AVFrame*, int*),
                         const char *encoderName)
{
    if (!oc || !ost || !ost->st) return;
    AVCodecContext *c = ost->st->codec;
    if (!c) return;
    for (;;) {
        AVPacket pkt = {0};
        int gotPacket = 0;
        av_init_packet(&pkt);
        int ret = encodeFunc(c, &pkt, NULL, &gotPacket);
        if (ret < 0) {
            LOGE("Flush %s encoder failed: %s", encoderName, av_err2str(ret));
            av_packet_unref(&pkt);
            break;
        }
        if (!gotPacket) { av_packet_unref(&pkt); break; }
        ret = WriteFrame(oc, &c->time_base, ost->st, &pkt);
        av_packet_unref(&pkt);
        if (ret < 0) {
            LOGE("Flush %s encoder write failed: %s", encoderName, av_err2str(ret));
            break;
        }
    }
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:

【AI-Review】【一般】【基础代码问题】【性能和效率问题】FlushVideoEncoder/FlushAudioEncoder 使用已废弃的 FFmpeg API

● 问题:新增的 FlushVideoEncoder 和 FlushAudioEncoder 使用了 avcodec_encode_video2 和 avcodec_encode_audio2。这两个 API 在 FFmpeg 3.1+ 中已被标记为 deprecated,推荐使用 avcodec_send_frame + avcodec_receive_packet 的组合。虽然当前代码库可能仍在使用旧 API(如 HandleEncodeWritePicture 中的 avcodec_encode_video2),但新增代码应避免使用废弃 API,以减少未来升级 FFmpeg 版本时的迁移工作量。

● 影响:一般。使用废弃 API 不会立即导致功能问题,但会增加未来 FFmpeg 版本升级的迁移成本,且旧 API 可能在未来版本中被移除。

● 建议:使用推荐的 avcodec_send_frame(NULL) + avcodec_receive_packet 模式进行编码器刷出:

static void FlushVideoEncoder(AVFormatContext *oc, OutputStream *ost)
{
    AVCodecContext *c;
    int ret;
    if (!oc || !ost || !ost->st) return;
    c = ost->st->codec;
    if (!c) return;
    ret = avcodec_send_frame(c, NULL);
    if (ret < 0 && ret != AVERROR_EOF) {
        LOGE("Flush video encoder send failed: %s", av_err2str(ret));
        return;
    }
    while (true) {
        AVPacket pkt = {0};
        av_init_packet(&pkt);
        ret = avcodec_receive_packet(c, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            av_packet_unref(&pkt);
            break;
        }
        if (ret < 0) {
            LOGE("Flush video encoder receive failed: %s", av_err2str(ret));
            av_packet_unref(&pkt);
            break;
        }
        ret = WriteFrame(oc, &c->time_base, ost->st, &pkt);
        av_packet_unref(&pkt);
        if (ret < 0) {
            LOGE("Flush video encoder write failed: %s", av_err2str(ret));
            break;
        }
    }
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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+ 
387void CloseStream(AVFormatContext *oc, OutputStream *ost)487void 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++,否则走解码获取的pts594 //如果优先走默认帧率或获取的帧率大于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;
cpf-manager

【AI-Review】【一般】【基础代码问题】【稳定性问题】StartEncoderWrite 中 swsState 未显式初始化

● 问题: StartEncoderWrite 函数中 RecordVideoSwsState swsState 是局部栈变量,其 swsContext、lastSrcWidth、lastSrcHeight 字段未显式初始化。虽然 C++ 中带有默认值初始化(struct 定义中有 = nullptr 和 = 0),但 swsState 是局部变量,如果后续代码依赖这些字段为 0/nullptr 来做判断(如 UpdateRecordVideoSwsContext 中比较 fr->width != state->lastSrcWidth),未初始化的值可能导致首次比较错误。

● 影响: 一般。如果编译器未正确零初始化,首次 sws_getContext 调用可能使用错误的 outWidth/outHeight(0x0),导致 sws_scale 失败。

● 建议: 显式初始化 swsState:

RecordVideoSwsState swsState = {0};

或者使用 memset(&swsState, 0, sizeof(swsState));,确保所有字段为零初始化。

likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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;
cpf-manager

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】srcFormat 为零时 StartEncoderWrite 的 fallback 路径使用零分辨率

● 问题: PR 删除了 record_video_frame 和 RecordMediaCodecVideoFrame 中对 srcFormat.height/width 的实时更新,改为在 ffp_start_record 中清零并在 InitRecordOutputResolution 中从已有帧初始化。但 StartEncoderWrite 中新增的 fallback 路径 swsState.outWidth = videoCodec ? videoCodec->width : recordWriteData->srcFormat.width 在 videoCodec 为 NULL 时回退到 srcFormat.width。如果录制开始时只有音频帧、没有视频帧(InitRecordOutputResolution 找不到视频帧),则 srcFormat.width/height 为 0,导致 sws_getContext 以 0x0 目标分辨率创建上下文,后续 sws_scale 必定失败。

● 影响: 严重。纯音频流或首帧非视频帧的场景下,录制输出文件可能损坏或录制失败。

● 建议: 在 StartEncoderWrite 中增加对 outWidth/outHeight 为 0 的防护:

swsState.outWidth = videoCodec ? videoCodec->width : recordWriteData->srcFormat.width;
swsState.outHeight = videoCodec ? videoCodec->height : recordWriteData->srcFormat.height;
if (swsState.outWidth <= 0 || swsState.outHeight <= 0) {
    LOGE("StartEncoderWrite: invalid output resolution %dx%d", swsState.outWidth, swsState.outHeight);
    return;
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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
608void ReleaseResources(AVFormatContext *oc, RecordWriteData *recordWriteData, AVOutputFormat *fmt,727void 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 
625int CheckAVFormatContext(AVFormatContext *oc, FFPlayer *mFFPlayer, const char *fileName)750int CheckAVFormatContext(AVFormatContext *oc, FFPlayer *mFFPlayer, const char *fileName)
@@ -651,7 +776,8 @@ RecordWriteData* WaitCacheVideoFrames(FFPlayer *mFFPlayer)
651int WriteHeader(FFPlayer *mFFPlayer, AVDictionary *opt, int frameRate)776int 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) {
cpf-manager

【AI-Review】【一般】【基础代码问题】【代码逻辑错误】WriteHeader 仍直接调用 CalculateFrameRate 可能返回负帧率

● 问题:av_dict_set_int(&opt, "video_track_timescale", CalculateFrameRate(mFFPlayer), 0) 仍直接调用 CalculateFrameRate,未使用本 PR 新增的 GetSafeRecordFrameRate。CalculateFrameRate 在 PTS 乱序时可返回负值,将负值设为 video_track_timescale 可能导致编码器行为异常。本 PR 已将 OpenVideo、VideoAudioStreamAndAvcodecOpen、WriteRecordFile 中的帧率获取统一替换为 GetSafeRecordFrameRate,但 WriteHeader 遗漏了。

● 影响:一般。在 PTS 乱序的异常视频流中,可能向编码器设置负 timescale 值,导致录制输出文件损坏。

● 建议:统一使用 GetSafeRecordFrameRate:

if (frameRate <= 0) {
    av_dict_set_int(&opt, "video_track_timescale", GetSafeRecordFrameRate(mFFPlayer), 0);
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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+ }
cpf-manager

【AI-Review】【一般】【基础代码问题】【代码逻辑错误】FindRecordResult 先 find 再 wait_for 存在通知丢失风险

● 问题:PR 修改了 FindRecordResult,在 wait_for 之前先查找 ijkplayerRecordResultMap。逻辑意图是:如果结果已在之前写入map,直接返回避免不必要的等待。但现有实现存在通知丢失风险——如果在 find 返回 end() 之后、wait_for 调用之前,另一个线程调用了 UpdateRecordResult 写入结果并 notify_one(),那么 wait_for 将错过这个通知,一直等到超时才返回失败。这是因为 unique_lock 在 find 之前就已持有,但 UpdateRecordResult 中的 lock_guard 也会争抢同一把锁,所以实际上不会并发。但更根本的问题是:ffp_stop_record 中新增的 pthread_join 确保 WriteRecordFile 已完成,那么在 pthread_join 返回时 UpdateRecordResult 应该已经被调用。此时 FindRecordResult 的 find 一定能命中,wait_for 分支永远不会执行。这意味着 wait_for 逻辑变得冗余,但不会导致功能错误。

● 影响:一般。逻辑冗余,不会导致功能错误,但增加了代码理解的复杂度。如果未来移除 ffp_stop_record 中的 pthread_join,则 wait_for 超时可能导致录制结果丢失。

● 建议:如果 ffp_stop_record 中保留 pthread_join,可以简化 FindRecordResult 的逻辑——在 ffp_stop_record 调用路径下结果必定已写入,直接 find 即可。如果后续移除 pthread_join,则需要将 find 和 wait_for 合并在同一个锁区间内避免竞态:

std::unique_lock<std::mutex> lock(ijkplayerRecordResultMutex);
auto it = ijkplayerRecordResultMap.find(ffp);
if (it != ijkplayerRecordResultMap.end()) {
    return it->second;
}
if (ijkplayerRecordResultCondition.wait_for(lock,
    std::chrono::milliseconds(RECORD_RESULT_TIMEOUT_MS)) == std::cv_status::no_timeout) {
    it = ijkplayerRecordResultMap.find(ffp);
    if (it != ijkplayerRecordResultMap.end()) {
        return it->second;
    }
}
likedislike
linsanjiu
linsanjiu
Jun 26 Comments:
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 }