今天遇到个应用在S2机器上拖动播放随机花屏的问题,其他机子都没有。那么如何分析花屏问题呢,我这里主要分析点播和本地文件播放导致的花屏,并不涉及直播花屏分析?
分析花屏问题无外乎从==解复用->解码->显示==这三步开始排查。(好吧,这个花屏问题真的让人蛋疼啊)
1.排查是否显示问题
先从显示开始排查,可以直接把解码后的数据编码成图片然后取出来,如果取出来的图片是花屏的,那么就继续往解码以及解复用开始排查。如果取出来的图片不是花屏的,那么就可以判断为显示导致的花屏。
先看下在jik中对解码后的帧编码图片取到本地的实现方式:
我们需要先获取到软解码后的帧数据,在ff_ffplay的ffplay_video_thread函数中,这里直接取。
int i = 0;//作为编码成图片的帧序列号,函数外
...
...
for (;;) {
//软解码获取到解码后的视频帧
ret = get_video_frame(ffp, frame);
if (ret < 0)
goto the_end;
if (!ret)
continue;
//add by hxk,花屏检测的实现方式
#if 0
AVFrame *pFrameRGB;
pFrameRGB = av_frame_alloc();//申请内存
if(pFrameRGB == NULL) {
av_log(NULL, AV_LOG_ERROR, "pFrameRGB malloc failed!\n");
return;
}
int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, is->viddec.avctx->width, is->viddec.avctx->height);
uint8_t * buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture*)pFrameRGB, buffer, AV_PIX_FMT_RGB24, is->viddec.avctx->width, is->viddec.avctx->height);
struct SwsContext* img_convert_ctx;
img_convert_ctx = sws_getContext(is->viddec.avctx->width,is->viddec.avctx->height, frame->format,
is->viddec.avctx->width, is->viddec.avctx->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t * const *)frame->data, (const int *)frame->linesize, 0, is->viddec.avctx->height,
pFrameRGB->data, pFrameRGB->linesize);
SaveFrame(pFrameRGB, is->viddec.avctx->width, is->viddec.avctx->height, i++);
av_free(buffer);
av_free(pFrameRGB);
sws_freeContext(img_convert_ctx);
#endif
if (ffp->get_frame_mode) {
if (!ffp->get_img_info || ffp->get_img_info->count <= 0) {
av_frame_unref(frame);
continue;
}
last_dst_pts = dst_pts;
if (dst_pts < 0) {
dst_pts = ffp->get_img_info->start_time;
} else {
dst_pts += (ffp->get_img_info->end_time - ffp->get_img_info->start_time) / (ffp->get_img_info->num - 1);
}
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
pts = pts * 1000;
if (pts >= dst_pts) {
while (retry_convert_image <= MAX_RETRY_CONVERT_IMAGE) {
ret = convert_image(ffp, frame, (int64_t)pts, frame->width, frame->height);
if (!ret) {
convert_frame_count++;
break;
}
retry_convert_image++;
av_log(NULL, AV_LOG_ERROR, "convert image error retry_convert_image = %d\n", retry_convert_image);
}
retry_convert_image = 0;
if (ret || ffp->get_img_info->count <= 0) {
if (ret) {
av_log(NULL, AV_LOG_ERROR, "convert image abort ret = %d\n", ret);
ffp_notify_msg3(ffp, FFP_MSG_GET_IMG_STATE, 0, ret);
} else {
av_log(NULL, AV_LOG_INFO, "convert image complete convert_frame_count = %d\n", convert_frame_count);
}
goto the_end;
}
} else {
dst_pts = last_dst_pts;
}
av_frame_unref(frame);
continue;
}
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
av_frame_unref(frame);
#if CONFIG_AVFILTER
}
下面实现保存帧成ppm图片到本地文件中
/**
*保存yuv成ppm图片格式到本地
**/
void SaveFrame(AVFrame *pFrame, int width, int height, int64_t iFrame)
{
av_log(NULL, AV_LOG_ERROR, "saveFrame:width:%d,height:%d,iFrame:%lld\n",width,height,iFrame);
FILE *pFile;
char szFilename[32];
sprintf(szFilename, "sdcard/frame%lld.ppm", iFrame);
pFile = fopen(szFilename,"wb+");
if (pFile == NULL) {
av_log(NULL, AV_LOG_ERROR, "pFile is null!\n");
return;
}
if (pFrame == NULL) {
av_log(NULL, AV_LOG_ERROR, "pFrame data is null!\n");
return;
}
if (pFrame->data == NULL) {
av_log(NULL, AV_LOG_ERROR, "pFrame data is null!\n");
return;
}
fprintf(pFile,"P6\n%d %d\n255\n",width,height);
//写文件到本地
for (int y = 0;y < height;y++) {
fwrite(pFrame->data[0] + y * pFrame->linesize[0],1,width * 3,pFile);
}
fclose(pFile);
}
这样就可以直接查看图片判断是否是显示的问题了。 如果不是显示的问题,下面可以去查看解复用器,是否是解复用器导致的花屏。
2.解复用导致的花屏
由于我们的视频是自定义的视频容器,存在一些兼容性问题。 可以查看解复用器中对关键帧的处理,以及seek时查找关键帧是否正确。
这里以我遇到的举例,seek时花屏,发现在ffplay上seek没有花屏,但是在ijk上seek花屏,最后定位到查找关键帧的索引错了。(rk机子让人蛋疼啊)。最后处理的是不是box中关键帧的enrty的flag需要置0,不然在rk机器上会被随机赋值成其他值,导致seek的时候判断关键帧的逻辑取了错误的关键帧,导致花屏。
static int imvoc_read_trak(imvoc_context *c, AVIOContext *pb, imvoc_box_t atom)
{
int ret = imvoc_read_default(c, pb, atom);
if (ret== 0)
{
AVStream *st = c->fc->streams[c->fc->nb_streams-1];
st->nb_index_entries = c->pCurTrack->spsz.entryCount;
st->index_entries_allocated_size = st->nb_index_entries*sizeof(AVIndexEntry);
st->index_entries = av_malloc(st->index_entries_allocated_size);
if (st->index_entries == NULL)
{
av_log(c->fc, AV_LOG_ERROR, "imvoc_read_trak av_malloc error\n");
return AVERROR(ENOMEM);
}
else
{
int64_t curTimestamp = 0;
int lastraacIndex = 1;
unsigned int nextkeyFrame = 0;
for(int i = 0; i < st->nb_index_entries; i++)
{
AVIndexEntry *pEntry = &st->index_entries[i];
pEntry->size = c->pCurTrack->spsz.sampleEntry[i];
pEntry->pos = c->pCurTrack->spof.sampleEntry[i];
pEntry->timestamp = curTimestamp;
//fix by hxk,Solve audio and video synchronization
if(STCODECP(st)->codec_type == AVMEDIA_TYPE_VIDEO)
{
//hxk:some old video should use thie method to get pts,old video's format is mepeg4
if(st->codecpar->codec_id == AV_CODEC_ID_MPEG4){
if(c->pCurTrack == NULL || c->pCurTrack->frameRate == 0){
av_log(c->fc, AV_LOG_ERROR, "c->pCurTrack is null or pCurTrack->frameRate == 0!\n");
return AVERROR(ENOMEM);
}
curTimestamp = (int64_t)i * 1000 / c->pCurTrack->frameRate;
}
else
{
//av_log(c->fc, AV_LOG_ERROR, "no mpeg4!\n");
curTimestamp += c->pCurTrack->spdt.sampleEntry[i];
}
}
else
{
curTimestamp += c->pCurTrack->spdt.sampleEntry[i];
}
//fix end
// curTimestamp += c->pCurTrack->spdt.sampleEntry[i];
if (STCODECP(st)->codec_type == AVMEDIA_TYPE_AUDIO)
{
pEntry->flags = AVINDEX_KEYFRAME;
}
else if(STCODECP(st)->codec_type == AVMEDIA_TYPE_VIDEO && i == nextkeyFrame)
{
pEntry->flags = AVINDEX_KEYFRAME;
if(c->pCurTrack->raac.sampleEntry != NULL &&
lastraacIndex < lastraacIndex < c->pCurTrack->raac.entryCount)
{
nextkeyFrame = c->pCurTrack->raac.sampleEntry[lastraacIndex++];
}
} else {
pEntry->flags = 0;//fix by hxk,如果不置0,在一些机器上会被设置成其他值,导致seek时取关键帧错误
}
}
// av_log(c->fc, AV_LOG_INFO, "timestap:%d\n", (int)st->index_entries[st->nb_index_entries-1].timestamp);
}
}
return 0;
}
3.解码器导致的花屏
这里区分为软解码和硬解码,软解码导致的花屏目前没遇到,可以先看硬解码导致的花屏问题。 这里主要集中在不同的解码器中赋值给mediacodec的sps和pps的值不对,以及mediacodec相关的参数设置不对导致解码花屏。
4.渲染导致的花屏
渲染导致的花屏一般有:
- 解码后的视频帧没有适配好,没有做针对屏幕大小做对应的缩放处理等
- 渲染格式不支持等,如解码后的视频是I420的,但是渲染实现只支持NV21格式等
- 纹理buffer没清除等导致的渲染花屏。
5.其他情况导致的一些花屏
- 码率过低,导致整个全局视频花屏,需要调高编码视频的码率。
- 视频参数问题,比如当视频源修改过视频参数(如从720P修改1080P),此时客户端用于解码的SPS&PPS如果没有重新获取的话,就会出现整个画面花屏的现象。这种花屏的现象会一直持续下去,不会随着时间而恢复正常画面。
- P帧丢失,I帧正常丢失P帧的情况下,画面的大部分区域是正常的,只有在发生变化的那部分区域会存在局部花屏。