【发布时间】: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->width * m_avFrame->height * 4的缓冲区(应该放在decodedFrame->size中)。最后,确保decodedFrame->data在Java 使用它的整个期间保持活动,否则你的程序仍然会崩溃。例如,您可以将decodedFrame存储在对象中的某个位置。 -
@Botje 好的,所以我们必须进行一些代码清理和一些结构修复来实现 RGBA(由于某种原因,Alpha 是最后一个)。问题现已解决,流工作正常。还处理了导致 OOM 异常抛出的代码中的一些内存泄漏。非常感谢您指出格式。 (不再使用 sizeof() 了,这是一个模糊的尝试,只是停留在那里......)
标签: android c++ ffmpeg java-native-interface