You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pjSIP Android端视频静音时发送静态图片的实现咨询

使用pjSIP JNI实现视频静音时发送静态图片的方案

pjSIP本身没有直接提供替换视频源为静态图的JNI接口,但可以通过劫持视频捕获回调的方式实现该功能,核心思路是在静音时替换摄像头帧为预准备的静态图片YUV帧,以下是具体实现步骤:

1. 预准备静态图片的YUV数据

pjSIP视频流通常使用YUV420格式,需要先将静态图片转换为该格式:

  • 在Android层将Bitmap转为YUV420字节数组:
    public static byte[] bitmapToYUV420(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        byte[] yuvBuffer = new byte[width * height * 3 / 2];
        int[] argbPixels = new int[width * height];
        bitmap.getPixels(argbPixels, 0, width, 0, 0, width, height);
    
        int yIndex = 0;
        int uvIndex = width * height;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int pixel = argbPixels[i * width + j];
                // 计算Y分量
                int y = ((pixel >> 16) & 0xFF) * 66 + ((pixel >> 8) & 0xFF) * 129 + (pixel & 0xFF) * 25 + 128 >> 8;
                yuvBuffer[yIndex++] = (byte) (Math.max(0, Math.min(255, y)));
                // 每2x2像素计算一次UV分量
                if (i % 2 == 0 && j % 2 == 0) {
                    int u = ((pixel >> 16) & 0xFF) * -38 + ((pixel >> 8) & 0xFF) * -74 + (pixel & 0xFF) * 112 + 128 >> 8;
                    int v = ((pixel >> 16) & 0xFF) * 112 + ((pixel >> 8) & 0xFF) * -94 + (pixel & 0xFF) * -18 + 128 >> 8;
                    yuvBuffer[uvIndex++] = (byte) (Math.max(0, Math.min(255, u)));
                    yuvBuffer[uvIndex++] = (byte) (Math.max(0, Math.min(255, v)));
                }
            }
        }
        return yuvBuffer;
    }
    
  • 将转换后的YUV数据通过JNI传递到C层,保存为全局静态帧变量:
    static pjmedia_frame s_static_yuv_frame;
    
    JNIEXPORT void JNICALL Java_com_your_package_VideoHelper_initStaticFrame(JNIEnv *env, jobject thiz,
                                                                             jbyteArray yuvData, jint width, jint height) {
        jbyte *yuvBuf = (*env)->GetByteArrayElements(env, yuvData, NULL);
        size_t bufSize = width * height * 3 / 2;
    
        s_static_yuv_frame.buf = pj_pool_alloc(pjmedia_get_default_pool(), bufSize);
        pj_memcpy(s_static_yuv_frame.buf, yuvBuf, bufSize);
        s_static_yuv_frame.size = bufSize;
        s_static_yuv_frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
        s_static_yuv_frame.timestamp = 0;
        s_static_yuv_frame.width = width;
        s_static_yuv_frame.height = height;
    
        (*env)->ReleaseByteArrayElements(env, yuvData, yuvBuf, 0);
    }
    

2. 劫持视频捕获回调

pjSIP通过pjmedia_vid_dev管理视频设备,我们可以替换其捕获回调函数,在静音时返回静态帧:

  • 保存原始捕获回调(通话建立后获取):
    static pjmedia_vid_dev_cap_cb s_original_cap_cb;
    static void *s_original_cap_user_data;
    
    // 初始化时保存原始回调
    void saveOriginalVideoCapCallback(pjmedia_vid_dev *vid_dev) {
        pjmedia_vid_dev_get_cap_cb(vid_dev, &s_original_cap_cb, &s_original_cap_user_data);
    }
    
  • 实现静态帧捕获回调:
    static pj_status_t staticFrameCapCallback(pjmedia_vid_dev *dev, pjmedia_frame *frame) {
        // 复制静态帧数据到输出帧,保证分辨率一致
        if (frame->width == s_static_yuv_frame.width && frame->height == s_static_yuv_frame.height) {
            pj_memcpy(frame->buf, s_static_yuv_frame.buf, frame->size);
            frame->timestamp = pj_get_timestamp(); // 生成合法时间戳,避免对端同步问题
            return PJ_SUCCESS;
        }
        return PJ_EINVAL;
    }
    
  • 实现静音切换的JNI接口:
    JNIEXPORT void JNICALL Java_com_your_package_VideoHelper_setVideoMute(JNIEnv *env, jobject thiz,
                                                                          jboolean isMuted) {
        pjmedia_vid_dev *currentVidDev = getCurrentActiveVideoDevice(); // 自行实现获取当前视频设备的逻辑
        if (!currentVidDev) return;
    
        if (isMuted) {
            pjmedia_vid_dev_set_cap_cb(currentVidDev, staticFrameCapCallback, NULL);
        } else {
            pjmedia_vid_dev_set_cap_cb(currentVidDev, s_original_cap_cb, s_original_cap_user_data);
        }
    }
    

3. 关键注意事项

  • 确保静态帧的分辨率、帧率与摄像头流一致,否则对端会出现显示异常;
  • 时间戳必须使用pj_get_timestamp()生成,保证视频流时序正确,避免对端卡顿;
  • 需在通话建立完成后再替换回调,避免空指针或无效设备操作;
  • 静态帧内存需使用pjSIP的内存池分配(pj_pool_alloc),避免内存泄漏。

内容的提问来源于stack exchange,提问作者Technocrat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:31:05