【问题标题】:Passing FFMPEG AvFrame data from c++ to JAVA将 FFMPEG AvFrame 数据从 c++ 传递到 JAVA
【发布时间】:2019-11-18 12:12:26
【问题描述】:

我需要将 FFMPEG“原始”数据传回我的 JAVA 代码,以便在屏幕上显示它。 我有一个处理 FFMPEG 的本机方法,然后在 java 中调用一个将 Byte[](到目前为止)作为参数的方法。

传递的字节数组由 JAVA 读取,但在执行 BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length); 时返回 null。我已经打印出数组,我得到了 200k 个元素(这是预期的),但无法解码。到目前为止,我正在做的是将数据从AvFrame->data 转换为unsigned char *,然后将其转换为jbyterArray。在所有转换之后,我将 jbyteArray 作为参数传递给我的 JAVA 方法。我在这里缺少什么吗?为什么BitmapFactory不把数组解码成图片显示?

编辑 1.0

目前我正在尝试通过

public void setImage(ByteBuffer bmp) {
        bmp.rewind();
        Bitmap bitmap = Bitmap.createBitmap(1920, 1080, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(bmp);

        runOnUiThread(() -> {
            ImageView imgViewer = findViewById(R.id.mSurfaceView);
            imgViewer.setImageBitmap(bitmap);

        });
    }

但我不断收到异常

JNI DETECTED ERROR IN APPLICATION: JNI NewDirectByteBuffer called with pending exception java.lang.RuntimeException: Buffer not large enough for pixels
at void android.graphics.Bitmap.copyPixelsFromBuffer(java.nio.Buffer) (Bitmap.java:657)
at void com.example.asmcpp.MainActivity.setSurfaceImage(java.nio.ByteBuffer) 

编辑 1.1

所以,这里是每次有帧传入时执行的完整代码。请注意,ByteBuffer 是在此方法中创建和传递的


void VideoClientInterface::onEncodedFrame(video::encoded_frame_t &encodedFrame) {
    AVFrame *filt_frame = av_frame_alloc();
    auto frame = std::shared_ptr<video::encoded_frame_t>(new video::encoded_frame_t,
                                                         [](video::encoded_frame_t *p) { if (p) delete p; });
    if (frame) {
        frame->size = encodedFrame.size;
        frame->ssrc = encodedFrame.ssrc;
        frame->width = encodedFrame.width;
        frame->height = encodedFrame.height;
        frame->dataType = encodedFrame.dataType;
        frame->timestamp = encodedFrame.timestamp;
        frame->frameIndex = encodedFrame.frameIndex;
        frame->isKeyFrame = encodedFrame.isKeyFrame;
        frame->isDroppable = encodedFrame.isDroppable;

        frame->data = new char[frame->size];
        if (frame->data) {
            memcpy(frame->data, encodedFrame.data, frame->size);
            AVPacket packet;
            av_init_packet(&packet);

            packet.dts = AV_NOPTS_VALUE;
            packet.pts = encodedFrame.timestamp;

            packet.data = (uint8_t *) encodedFrame.data;
            packet.size = encodedFrame.size;

            int ret = avcodec_send_packet(m_avCodecContext, &packet);
            if (ret == 0) {
                ret = avcodec_receive_frame(m_avCodecContext, m_avFrame);
                if (ret == 0) {
                    m_transform = sws_getCachedContext(
                            m_transform, // previous context ptr
                            m_avFrame->width, m_avFrame->height, AV_PIX_FMT_YUV420P, // src
                            m_avFrame->width, m_avFrame->height, AV_PIX_FMT_RGB24, // dst
                            SWS_BILINEAR, nullptr, nullptr, nullptr // options
                    );

                    auto decodedFrame = std::make_shared<video::decoded_frame_t>();
                    decodedFrame->width = m_avFrame->width;
                    decodedFrame->height = m_avFrame->height;
                    decodedFrame->size = m_avFrame->width * m_avFrame->height * 3;
                    decodedFrame->timeStamp = m_avFrame->pts;

                    decodedFrame->data = new unsigned char[decodedFrame->size];

                    if (decodedFrame->data) {
                        uint8_t *dstSlice[] = {decodedFrame->data,
                                               0,
                                               0};// outFrame.bits(), outFrame.bits(), outFrame.bits()

                        const int dstStride[] = {decodedFrame->width * 3, 0, 0};
                        sws_scale(m_transform, m_avFrame->data, m_avFrame->linesize,
                                                  0, m_avFrame->height, dstSlice, dstStride);

                        auto m_rawData = decodedFrame->data;
                        auto len = strlen(reinterpret_cast<char *>(m_rawData));
                        if (frameCounter == 10) {
                            jobject newArray = GetJniEnv()->NewDirectByteBuffer(m_rawData, len);
                            GetJniEnv()->CallVoidMethod(m_obj, setSurfaceImage, newArray);
                            frameCounter = 0;

                        }
                        frameCounter++;

                    }
                } else {
                    av_packet_unref(&packet);
                }
            } else {
                av_packet_unref(&packet);
            }
        }
    }
}

我不完全确定我是否正确地完成了那部分。如果您发现其中有任何错误,请随时指出。

【问题讨论】:

  • 我写了一个答案,假设您的意思是原始数组 (byte[]) 而不是对象数组 (Byte[])。
  • @Botje 这正是我所需要的。我正在尝试将您的答案合并到我的代码中。现在只处理 ByteBuffer UnsupportedOperationException 异常。
  • @Botje 根据您对答案的评论,用更多细节编辑了问题。
  • 对任意二进制数据使用strlen 没有多大意义。从您的代码中很明显,您收到了一个 RGB(24) 位图,而 android 想要一个 ARGB 图像。你需要让 FFmpeg 生成 ARGB(或 RGB8888),然后告诉 Java 你有一个大小为 m_avFrame-&gt;width * m_avFrame-&gt;height * 4 的缓冲区(应该放在 decodedFrame-&gt;size 中)。最后,确保decodedFrame-&gt;data 在Java 使用它的整个期间保持活动,否则你的程序仍然会崩溃。例如,您可以将decodedFrame 存储在对象中的某个位置。
  • @Botje 好的,所以我们必须进行一些代码清理和一些结构修复来实现 RGBA(由于某种原因,Alpha 是最后一个)。问题现已解决,流工作正常。还处理了导致 OOM 异常抛出的代码中的一些内存泄漏。非常感谢您指出格式。 (不再使用 sizeof() 了,这是一个模糊的尝试,只是停留在那里......)

标签: android c++ ffmpeg java-native-interface


【解决方案1】:

您不能将本机字节数组转换为 jbyteArray 并期望它能够工作。 byte[] 是具有 length 字段、引用计数等的实际对象。

使用NewDirectByteBuffer 代替将字节缓冲区包装到Java ByteBuffer 中,您可以从中使用.array() 获取实际的byte[]

请注意,此 JNI 操作相对昂贵,因此如果您希望在每帧基础上执行此操作,您可能需要预先分配一些字节缓冲区并告诉 FFmpeg 直接写入这些缓冲区。

【讨论】:

  • 所以,我正在获取ByteBuffer 对象,但在其上调用.array() 会向我抛出UnsupportedOperationException,并使一切崩溃。
  • 多哈。可能.array() 仅适用于包装byte[]ByteBuffers。 B计划:使用Bitmap#copyPixelsFromBuffer(在this answer中找到)
猜你喜欢
  • 2016-01-03
  • 1970-01-01
  • 2013-12-27
  • 1970-01-01
  • 1970-01-01
  • 2017-01-12
  • 1970-01-01
  • 2013-01-28
  • 2010-11-01
相关资源
最近更新 更多