ijkplayer实现图形字幕的播放

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nonmarking/article/details/85170454

bitmap类型字幕多见于蓝光片源。但是在原生ijkplayer中,只有针对文本类型字幕的处理,而不支持bitmap类型字幕,相关代码如下

//static void video_image_display2(FFPlayer *ffp) @ ff_ffplay.c
if (is->subtitle_st) {
            if (frame_queue_nb_remaining(&is->subpq) > 0) {
                sp = frame_queue_peek(&is->subpq);
                if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
                    if (!sp->uploaded) {
                        if (sp->sub.num_rects > 0) {
                            char buffered_text[4096];
                            if (sp->sub.rects[0]->text) {   //在这里只对text类型和ass类型的字幕做了相应的的处理
                                strncpy(buffered_text, sp->sub.rects[0]->text, 4096);
                            }
                            else if (sp->sub.rects[0]->ass) {
                                parse_ass_subtitle(sp->sub.rects[0]->ass, buffered_text);
                            }
                            ffp_notify_msg4(ffp, FFP_MSG_TIMED_TEXT, 0, 0, buffered_text, sizeof(buffered_text));
                        }
                        sp->uploaded = 1;
                    }
                }
            }
        }

当sp->sub.format==0时,对应的即为bitmap类型的字幕格式,如下定义

typedef struct AVSubtitle {
    uint16_t format; /* 0 = graphics */
    uint32_t start_display_time; /* relative to packet pts, in ms */
    uint32_t end_display_time; /* relative to packet pts, in ms */
    unsigned num_rects;
    AVSubtitleRect **rects;
    int64_t pts;    ///< Same as packet pts, in AV_TIME_BASE
} AVSubtitle;

因此,要让ijkplayer支持bitmap类型字幕,需要做以下几件事情:

1.在video_image_display2方法中根据sp→sub.format的值区分字幕类型,当是bitmap类型字幕时,取出相应的bmp像素数据

2.在jni层(ijkplayer_jni.c),利用拿到的bmp像素数据,调用android.graphics.Bitmap类中的方法,创建出相应的Bitmap对象

3.在java层(IjkMediaPlayer.java),拿到Bitmap对象,调用android.graphic.Canvas类的drawBitmap方法,绘制到单独的View中,从而显示出字幕的内容


下面来具体看一下每一步相关的代码

1.native层的修改,以PGS格式字幕为例

if (is->subtitle_st) {
            if (frame_queue_nb_remaining(&is->subpq) > 0) {
                sp = frame_queue_peek(&is->subpq);
 
                if (vp->pts >= sp->pts + ((float) sp->sub.start_display_time / 1000)) {
                    if (!sp->uploaded) {
                        if (sp->sub.num_rects > 0) {
                            if (sp->sub.format != 0) { //此时字幕类型为文本类型,还是按照原有逻辑处理
                                char buffered_text[4096];
                                if (sp->sub.rects[0]->text) {
                                    strncpy(buffered_text, sp->sub.rects[0]->text, 4096);
                                }
                                else if (sp->sub.rects[0]->ass) {
                                    parse_ass_subtitle(sp->sub.rects[0]->ass, buffered_text);
                                }
                                ffp_notify_msg4(ffp, FFP_MSG_TIMED_TEXT, 0, 0, buffered_text, sizeof(buffered_text));
                            } else { //此时字幕类型为bitmap类型
                                if (sp->sub.rects[0]) {
                                    if (sp->sub.rects[0]->data[0] && sp->sub.rects[0]->linesize[0] > 0) {
                                        //w和h记录了bitmap字幕的宽和高。sp->sub.rects[0]还有成员x和y,记录的是bitmap字幕的坐标,因为我们想要的是可以调整字幕位置,所以不需要这两个变量。
                                        int len = sp->sub.rects[0]->w * sp->sub.rects[0]->h;                                    
                                        //每个像素数据包含rgba四个字节
                                        uint8_t buffered_img[len*4];
                                        for(int i = 0; i < len; i++) {
                                            //因为是bitmap类型,所以像素数据在sp->sub.rects[0]中分两块存储,data[1]中保存的是色表(palette),data[0]中保存的色表索引值
                                            //从data[0]中拿到色表索引
                                            int coloridx = sp->sub.rects[0]->data[0][i] * 4;
                                            //到data[1]中拿到对应的颜色值
                                            buffered_img[i*4] = sp->sub.rects[0]->data[1][coloridx];//R
                                            buffered_img[i*4 + 1] = sp->sub.rects[0]->data[1][coloridx + 1];//G
                                            buffered_img[i*4 + 2] = sp->sub.rects[0]->data[1][coloridx + 2];//B
                                            buffered_img[i*4 + 3] = sp->sub.rects[0]->data[1][coloridx + 3];//A
                                        }
                                        //自定义一个新的消息类型FFP_MSG_BITMAP_SUBTITLE,用于传递像素数据
                                        ffp_notify_msg4(ffp, FFP_MSG_BITMAP_SUBTITLE, sp->sub.rects[0]->w, sp->sub.rects[0]->h, buffered_img, len*4);
                                    }
                                }
                            }
                        }
                        sp->uploaded = 1;
                    }
                }
            }
        }

2.jni层的修改,在message_loop_n方法中,添加

case FFP_MSG_BITMAP_SUBTITLE:
            if (msg.obj) {
                int _width = msg.arg1;
                int _height = msg.arg2;
                uint8_t* bitmap = (uint8_t*) msg.obj;
                jclass bitmapConfig = (*env)->FindClass(env, "android/graphics/Bitmap$Config");
                jfieldID rgba8888FieldID = (*env)->GetStaticFieldID(env, bitmapConfig, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
                jobject rgba8888Obj = (*env)->GetStaticObjectField(env, bitmapConfig, rgba8888FieldID);
 
 
                jclass bitmapClass = (*env)->FindClass(env, "android/graphics/Bitmap");
                jmethodID createBitmapMethodID = (*env)->GetStaticMethodID(env, bitmapClass,"createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
                jobject bitmapObj = (*env)->CallStaticObjectMethod(env, bitmapClass, createBitmapMethodID, _width, _height, rgba8888Obj);
 
                jintArray pixels = (*env)->NewIntArray(env, _width * _height);
                for (int i = 0; i < _width * _height; i++)
                {
                    unsigned char red = bitmap[i*4];
                    unsigned char green = bitmap[i*4 + 1];
                    unsigned char blue = bitmap[i*4 + 2];
                    unsigned char alpha = bitmap[i*4 + 3];
                    int currentPixel = (alpha << 24) | (red << 16) | (green << 8) | (blue);
                    (*env)->SetIntArrayRegion(env, pixels, i, 1, &currentPixel);
                }
 
                jmethodID setPixelsMid = (*env)->GetMethodID(env, bitmapClass, "setPixels", "([IIIIIII)V");
                (*env)->CallVoidMethod(env, bitmapObj, setPixelsMid, pixels, 0, _width, 0, 0, _width, _height);
                post_event2(env, weak_thiz, MEDIA_BITMAP_SUBTITLE, msg.arg1, msg.arg2, bitmapObj);//自定义MEDIA_BITMAP_SUBTITLE消息,用于传递Bitmap对象
                J4A_DeleteLocalRef__p(env, &bitmapObj);
            } else {
                post_event2(env, weak_thiz, MEDIA_BITMAP_SUBTITLE, 0, 0, NULL);
            }
            break;

3.java层的修改,在IjkMediaPlayer.java的EventHandler中,添加

case MEDIA_BITMAP_SUBTITLE:
    if (msg.obj == null) {
        player.notifyOnTimedText(null);
    } else {
        int bitmapWidth = msg.arg1;
        int bitmapHeight = msg.arg2;
        //拿到jni层传递过来的Bitmap对象
        Bitmap bitmap = (Bitmap) msg.obj;
        // Build the cue.
        int planeWidth = player.mVideoWidth;
        int planeHeight = player.mVideoHeight;
        int bitmapX = (planeWidth - bitmapWidth) / 2;//默认在水平居中,靠近画面底部的位置显示字幕,所以可以简单计算出一个坐标值
        int bitmapY = planeHeight - bitmapHeight - 50;
        //创建字幕对象,稍后讲解
        IjkSubtitle subtitle = new IjkSubtitle(
                bitmap,
                (float) bitmapX / planeWidth,
                IjkSubtitle.ANCHOR_TYPE_START,
                (float) bitmapY / planeHeight,
                IjkSubtitle.ANCHOR_TYPE_START,
                (float) bitmapWidth / planeWidth,
                (float) bitmapHeight / planeHeight);
        //通知到IjkVideoView中,进行显示
        player.notifyOnTimedText(subtitle);
    }
    return;

在上面用到了一个IjkSubtitle类,这个类移植自ExoPlayer的com/google/android/exoplayer2/text/Cue.java。在ExoPlayer中有一套完整的图片与文字字幕渲染、样式修改的工具类,所以可以很方便的移植过来使用,包含以下几个类

com/google/android/exoplayer2/text/CaptionStyleCompat.java

com/google/android/exoplayer2/ui/SubtitleView.java

com/google/android/exoplayer2/ui/SubtitlePainter.java

由此,即完成了图片字幕的显示

猜你喜欢

转载自blog.csdn.net/nonmarking/article/details/85170454
今日推荐