各位兄弟姐妹:
这几天一直困扰一个问题,在这里请教大家。
我在做一个安卓的app(以后可能也会迁移到IOS),采用Android Studio+tensorflow Lite进行运动识别(已经顺利完成),但是想将视频和视频上面的识别(在SurfaceView中 ,采用canvas绘制)结果导出为mp4,这个该怎么实现呢?期待朋友的指导。
可以使用Android中的MediaCodec和MediaMuxer类进行视频编码和混合,将每一帧的识别结果与原始帧合并,把编码后的视频帧写入输出缓冲区,再将编码后的视频帧通过MediaMuxer类的writeSampleData()方法写入新文件中
原本导出的文件类型是怎样的
需要写个工具或者用插件来转换
将视频和视频上的识别结果导出为 mp4 可以采用 FFmpeg 库进行处理。
FFmpeg 是一个用于处理多媒体数据的开源库,它包含了用于音视频编解码、转码、过滤等的多个工具和库,支持多种视频格式和编码方式,可在命令行中调用。
具体实现过程如下:
在 Android Studio 中添加 FFmpeg 库
可以使用以下两种方式之一:
在 build.gradle 文件中添加 FFmpeg 的依赖
groovy
implementation 'com.arthenica:mobile-ffmpeg-full-gpl:4.5.0.LTS'
将 FFmpeg 库文件放置到 Android 项目中
将 FFmpeg 库文件 libffmpeg.so 放置到 Android 项目的 app/src/main/jniLibs/ 目录下。
调用 FFmpeg 库生成 mp4 文件
使用以下命令调用 FFmpeg 库生成 mp4 文件:
shell
ffmpeg -y -f rawvideo -pixel_format argb -video_size WIDTHxHEIGHT -i - -vf "transpose=1,transpose=1,transpose=1" -c:v libx264 -preset slow -tune fastdecode -pix_fmt yuv420p -f mp4 OUTPUT.mp4
其中,rawvideo 是输入数据的格式,-pixel_format 指定像素格式,-video_size 指定视频尺寸,-i - 指定输入从标准输入读入,-vf 指定视频过滤器,-c:v 指定视频编码器,-preset 指定编码器预设,-tune 指定编码器参数,-pix_fmt 指定输出像素格式,-f mp4 指定输出格式为 mp4,最后的 OUTPUT.mp4 指定输出文件的路径和文件名。
在 Android 中调用 FFmpeg 库可以使用 ProcessBuilder 进行处理。例如:
java
ProcessBuilder pb = new ProcessBuilder(
"ffmpeg", "-y", "-f", "rawvideo", "-pixel_format", "argb",
"-video_size", "WIDTHxHEIGHT", "-i", "-", "-vf", "transpose=1,transpose=1,transpose=1",
"-c:v", "libx264", "-preset", "slow", "-tune", "fastdecode",
"-pix_fmt", "yuv420p", "-f", "mp4", "OUTPUT.mp4"
);
Process process = pb.start();
OutputStream stdin = process.getOutputStream();
// 将视频帧数据写入 stdin
// ...
stdin.flush();
stdin.close();
int ret = process.waitFor();
if (ret != 0) {
// 处理错误
}
其中,-i - 指定从标准输入读入,因此需要将视频帧数据写入 stdin 中。
我这边是用opencv,但是不是移动端的,而且图像识别速度没有1秒25帧,可能会卡
要将视频和视频上面的识别结果导出为mp4格式,可以尝试进行以下步骤:
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
其中,view
是指SurfaceView对象。绘制完成后,您可以获得一个包含SurfaceView内容的Bitmap对象。
MediaCodec mediaCodec = MediaCodec.createEncoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = mediaCodec.createInputSurface();
mediaCodec.start();
MediaMuxer mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
int trackIndex = -1;
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
while (true) {
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
Bitmap bitmap = getFrame();
if (bitmap != null) {
Canvas canvas = surface.lockCanvas(null);
canvas.drawBitmap(bitmap, 0, 0, null);
surface.unlockCanvasAndPost(canvas);
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
BufferInfo bufferInfo = new BufferInfo();
bufferInfo.presentationTimeUs = getPTSUs();
bufferInfo.flags |= CanvasMediaCodec.BUFFER_FLAG_KEY_FRAME;
bufferInfo.size = inputBuffer.limit();
inputBuffer.put(buffer);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags);
} else {
mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && bufferInfo.size != 0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
} else {
mediaMuxer.writeSampleData(trackIndex, outputBuffer, bufferInfo);
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
}
}
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
mediaCodec.stop();
mediaCodec.release();
mediaMuxer.stop();
mediaMuxer.release();
break;
}
}
其中,getFrame()
方法可以从Bitmap对象中按照一定的时间间隔获得一帧图像,getPTSUs()
方法用于记录帧时间戳,outputPath
是输出的视频文件路径,width
和height
是视频宽度和高度,bitRate
是比特率,fps
是帧率,iFrameInterval
是关键帧之间的间隔。
FileOutputStream outputStream = new FileOutputStream(outputPath);
outputStream.write(videoBytes);
outputStream.close();
其中,videoBytes
是将视频编码后得到的byte数组。
以上是实现将视频和视频上面的识别结果导出为mp4的基本步骤,具体实现会因为项目的具体细节而有所差异。希望这些信息对您有所帮助!