android - 将 FFMPEG AvFrame 数据从 C++ 传递到 JAVA

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

我需要将 FFMPEG“原始”数据传回我的 JAVA 代码,以便在屏幕上显示它。 我有一个处理 FFMPEG 的 native 方法,然后在 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);
            }
        }
    }
}

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

最佳答案

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

使用NewDirectByteBuffer而不是将字节缓冲区包装到 Java ByteBuffer ,从那里您可以使用 .array() 获取实际的 byte[]

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

关于android - 将 FFMPEG AvFrame 数据从 C++ 传递到 JAVA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58914293/

相关文章:

c++ - 在现有 UWP 项目中对代码进行单元测试的预期方式?

java - 判断链表是否是循环的

c++ - 非常慢的 ffmpeg/sws_scale() - 仅在重载时

android - ffmpeg 无法编译

android - 如何将指南针添加到 map View

java - 构造函数 Intent(FragmentTab1, Class<AlgbraHome>) 未定义

android - 我的应用程序中的 WebView 未加载 HTTPS URL

android - 是否可以在数据类 kotlin android 中使用不同类和数据类型的相同键?

c++ - 如果构造函数被显式默认或删除,为什么自 C++20 以来聚合初始化不再起作用?

c - FFmpeg - 音频编码在音频上产生额外的噪音