Android 播放视频并获取指定时间的帧画面

2024-06-10 07:18

本文主要是介绍Android 播放视频并获取指定时间的帧画面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android 播放视频并获取指定时间的帧画面

[摘要:比来做的项目请求既能播放视频(近似于视频播放器),又能每隔1s摆布猎取一帧视频绘里,然后对图片举行处置惩罚,观察了一周,也被熬煎了一周,总算找到了大抵相符请求的方式。起首对]

最近做的项目要求既能播放视频(类似于视频播放器),又能每隔1s左右获取一帧视频画面,然后对图片进行处理,调查了一周,也被折磨了一周,总算找到了大致符合要求的方法。首先对调查过程中涉及到的方法进行简单介绍,再重点介绍最终所采用的方法,话不多说,进入正题。

一.MediaMetadataRetriever

播放视频并取得画面的一帧,大家最先想到应该都是这个,我同样也最先对它进行了测试,这里使用MediaPlayer进行播放,视频播放界面使用SurfaceView来实现。

public class PlayerMainActivity extends Activity implements OnClickListener,SurfaceHolder.Callback, Runnable {private static final String TAG = "Movie";private MediaPlayer mediaPlayer;private SurfaceView surfaceView;private SurfaceHolder surfaceHolder;private Button play_btn;private int currentPosition = 0;private Bitmap bitmap = null;private String dataPath = Environment.getExternalStorageDirectory()    + "/Video/Test_movie.AVI";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);surfaceView = (SurfaceView) findViewById(R.id.surfaceView);play_btn = (Button) findViewById(R.id.play_btn);       play_btn.setOnClickListener(this);screen_cut_btn.setOnClickListener(this);
surfaceHolder = surfaceView.getHolder();surfaceHolder.addCallback(this);}@Overridepublic void run() {mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDisplay(surfaceHolder);try {mediaPlayer.setDataSource(dataPath);mediaPlayer.prepare();            MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();mediaMetadataRetriever.setDataSource(dataPath);int millis = mediaPlayer.getDuration();Log.i(TAG, "millis: " + millis/1000);for (int i = 10000*1000; i < 20*1000*1000; i+=500*1000) {bitmap = mediaMetadataRetriever.getFrameAtTime(i, MediaMetadataRetriever.OPTION_CLOSEST);String path = Environment.getExternalStorageDirectory() + "/bitmap/"    + i + ".png";FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path);bitmap.compress(CompressFormat.PNG, 100, fileOutputStream);Log.i(TAG, "i: " + i/1000/1000);} catch (Exception e) {Log.i(TAG, "Error: " + i/1000/1000);e.printStackTrace();}finally {if (fileOutputStream != null) {fileOutputStream.close();}}                bitmap.recycle();}} catch (Exception e) {e.printStackTrace();}}@Overridepublic void surfaceCreated(SurfaceHolder holder) {Thread t = new Thread(this);t.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,    int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}@Overridepublic void onClick(View view) {switch (view.getId()) {case R.id.play_btn:if (mediaPlayer.isPlaying()) {mediaPlayer.pause();play_btn.setText(getResources().getText(R.string.play));} else {mediaPlayer.start();play_btn.setText(getResources().getText(R.string.pause));}break;default:break;}}@Overrideprotected void onDestroy() {super.onDestroy();if (mediaPlayer.isPlaying()) {mediaPlayer.stop();}mediaPlayer.release();}
}

 

获取一帧的关键代码为:

Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST);

public Bitmap getFrameAtTime(long timeUs, int option) 

第一个参数是传入时间,只能是us(微秒)

第二个参数:

  • OPTION_CLOSEST    在给定的时间,检索最近一个帧,这个帧不一定是关键帧。
  • OPTION_CLOSEST_SYNC   在给定的时间,检索最近一个同步与数据源相关联的的帧(关键帧)。
  • OPTION_NEXT_SYNC 在给定时间之后检索一个同步与数据源相关联的关键帧。
  • OPTION_PREVIOUS_SYNC  顾名思义,同上

这里为了提取我们想要的帧,不使用关键帧,所以用 OPTION_CLOSEST .

 

最终的测试结果并不理想,连续取20帧画面,其中真正有效的只有7张,其余都是重复的,原因为即使是使用参数OPTION_CLOSEST,程序仍然会去取指定时间临近的关键帧,如10s-15s总是取同一帧,因此这种方法不可用。

提高视频的质量或许有效,未尝试

 

补充MediaMetadataRetriever的其他知识

// 取得视频的总长度(单位为毫秒)

String time = mediaMetadataRetriever. extractMetadata( MediaMetadataRetriever. METADATA_KEY_DURATION);

MediaMetadataRetriever主要用来取缩略图。

 

二.ThumbnailUtils

同样主要是用来获取视频的缩略图,不可靠,因此并未深入研究。

 

从以上两种方法可以看出,Android API 所提供的获取视频帧的方法大都只能获取视频的缩略图,没办法获取视频每一帧的图片。因此,调查方向应当转向对视频进行解码,然后获取任意一帧。

 

三.MediaCodec

硬件解码,尝试从inputBuffers、outputBuffers中获取帧画面,失败,bitmap中的数据大小始终为0 KB。

public class MoviePlayerActivity extends Activity implements OnTouchListener, OnClickListener, SurfaceHolder.Callback {private static final String TAG = "Image";private String file_path;private Button movie_play;private boolean playButtonVisible;private boolean playPause;private SurfaceView surfaceView;private SurfaceHolder surfaceHolder;private PlayerThread playerThread = null;private ByteBuffer mPixelBuf;@Overrideprotected void onCreate(Bundle savedInstanceState) {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);     
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);super.onCreate(savedInstanceState);setContentView(R.layout.movie_player_activity);movie_play = (Button) findViewById(R.id.movie_videoview_play);movie_play.setOnClickListener(this);movie_play.setText("Play");Intent intent = getIntent();file_path = intent.getStringExtra("file_path");  //此Activity是从其他页面跳转来的,file_path为要播放的视频地址surfaceView = (SurfaceView) findViewById(R.id.surfaceView);surfaceHolder = surfaceView.getHolder();surfaceHolder.addCallback(this);mPixelBuf = ByteBuffer.allocateDirect(640*480*4);mPixelBuf.order(ByteOrder.LITTLE_ENDIAN);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {if (playerThread == null) {playerThread = new PlayerThread(holder.getSurface());playerThread.start();}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,    int height) {        }@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {if (playerThread != null) {playerThread.interrupt();}}   @Overridepublic boolean onTouch(View v, MotionEvent event) {if (!playButtonVisible) {movie_play.setVisibility(View.VISIBLE);movie_play.setEnabled(true);} else {movie_play.setVisibility(View.INVISIBLE);}playButtonVisible = !playButtonVisible;return false;}@Overridepublic void onClick(View view) {switch (view.getId()) {case R.id.movie_videoview_play:if (!playPause) {movie_play.setText("Pause");} else {movie_play.setText("Play");}playPause = !playPause;break;default:break;}}private void writeFrameToSDCard(byte[] bytes, int i, int sampleSize) {                i++;if (i%10 == 0) {try {Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, sampleSize);mPixelBuf.rewind();bmp.copyPixelsFromBuffer(mPixelBuf);String path = Environment.getExternalStorageDirectory() + "/bitmap/" + i + ".png";FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path);bmp.compress(CompressFormat.PNG, 90, fileOutputStream);bmp.recycle();Log.i(TAG, "i: " + i);} catch (Exception e) {Log.i(TAG, "Error: " + i);e.printStackTrace();}finally {if (fileOutputStream != null) {fileOutputStream.close();}}} catch (Exception e) {e.printStackTrace();}}}private class PlayerThread extends Thread {private MediaExtractor extractor;private MediaCodec mediaCodec;private Surface surface;public PlayerThread(Surface surface) {this.surface = surface;}@Overridepublic void run() {extractor = new MediaExtractor();try {extractor.setDataSource(file_path);} catch (IOException e1) {Log.i(TAG, "Error");e1.printStackTrace();}for (int i = 0; i < extractor.getTrackCount(); i++) {MediaFormat format = extractor.getTrackFormat(i);String mime = format.getString(MediaFormat.KEY_MIME);if (mime.startsWith("video/")) {extractor.selectTrack(i);mediaCodec = MediaCodec.createDecoderByType(mime);mediaCodec.configure(format, surface, null, 0);break;}}if (mediaCodec == null) {Log.e(TAG, "Can't find video info!");return;}mediaCodec.start();ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();BufferInfo info = new BufferInfo();boolean isEOS = false;long startMs = System.currentTimeMillis();int i = 0;while (!Thread.interrupted()) {if (!isEOS) {int inIndex = mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {ByteBuffer buffer = inputBuffers[inIndex];int sampleSize = extractor.readSampleData(buffer, 0);if (sampleSize < 0) {// We shouldn't stop the playback at this point, just pass the EOS// flag to mediaCodec, we will get it again from the dequeueOutputBufferLog.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");mediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);isEOS = true;} else {mediaCodec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);extractor.advance();}}}int outIndex = mediaCodec.dequeueOutputBuffer(info, 100000);switch (outIndex) {case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");outputBuffers = mediaCodec.getOutputBuffers();break;case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:Log.d(TAG,"New format " + mediaCodec.getOutputFormat());break;case MediaCodec.INFO_TRY_AGAIN_LATER:Log.d(TAG, "dequeueOutputBuffer timed out!");break;default:ByteBuffer buffer = outputBuffers[outIndex];Log.v(TAG,"We can't use this buffer but render it due to the API limit, " + buffer);// We use a very simple clock to keep the video FPS, or the video// playback will be too fastwhile (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {try {sleep(10);} catch (InterruptedException e) {e.printStackTrace();break;}}mediaCodec.releaseOutputBuffer(outIndex, true);/* saves frame to SDcard */mPixelBuf.rewind();GLES20.glReadPixels(0, 0, 640, 480, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);try {ByteBuffer outByteBuffer = outputBuffers[outIndex];outByteBuffer.position(info.offset);
                        outByteBuffer.limit(info.offset + info.size);  //info的两个参数值始终为0,所保存的.png也都是0KB。outByteBuffer.limit(2);byte[] dst = new byte[outByteBuffer.capacity()];outByteBuffer.get(dst);writeFrameToSDCard(dst, i, dst.length);    i++;} catch (Exception e) {Log.d(TAG, "Error while creating bitmap with: " + e.getMessage());}break;}// All decoded frames have been rendered, we can stop playing nowif ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {Log.d(TAG,    "OutputBuffer BUFFER_FLAG_END_OF_STREAM");break;}}mediaCodec.stop();mediaCodec.release();extractor.release();}}
}

所保存的图片大小始终为0的原因大概和下面的方法五类似。

想要深入研究此方法可以参考:

http://stackoverflow.com/questions/19754547/mediacodec-get-all-frames-from-video

http://stackoverflow.com/questions/23321880/how-to-get-bitmap-frames-from-video-using-mediacodec

 

四.JCodec

http://jcodec.org/

jcodec-samples-0.1.7.apk

解码视频文件耗时较长,性能较差,平均1.5s取一帧图片,达不到要求。

 

五.使用VideoView播放视频,使用getDrawingCache获取View视图

VideoView是Android提供的播放视频组件,上手很容易。这里使用getDrawingCache获取控件的View。

但是DrawingCache只能截取非视频部分的画面,播放视频的那个小窗口一直是黑色的。原因为Activity画面走的是framebuffer,视频是硬解码推送过来的,所有读取/dev/graphics/fb0  视频播放的那一块就是黑色的,硬件解码不会推送到buffer的,而是直接推送到硬件输出了。

public class MoviePlayerActivity extends Activity implements OnTouchListener, OnClickListener, Runnable {private static final String TAG = "Image";private String file_path;private VideoView videoView;private Button movie_play;private boolean playButtonVisible;private boolean playPause;@Overrideprotected void onCreate(Bundle savedInstanceState) {// full screen
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// no title
        requestWindowFeature(Window.FEATURE_NO_TITLE);// landscape or horizontal screen
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);super.onCreate(savedInstanceState);setContentView(R.layout.movie_player_activity);movie_play = (Button) findViewById(R.id.movie_videoview_play);movie_play.setOnClickListener(this);movie_play.setText("Play");Intent intent = getIntent();file_path = intent.getStringExtra("file_path");videoView = (VideoView) findViewById(R.id.movie_palyer_videoview);videoView.setMediaController(null);// videoView.setMediaController(new MediaController(this));
        videoView.setVideoPath(file_path);videoView.start();videoView.requestFocus();Thread screenShootThread = new Thread(this);screenShootThread.start();videoView.setOnTouchListener(this);}@Overridepublic void run() {            //播放视频时后台自动截图,注意参数为videoViewfor (int i = 10000 * 1000; i < 20 * 1000 * 1000; i += 500 * 1000) {int nowTime = videoView.getCurrentPosition();try {screenShot(videoView, i);} catch (Exception e1) {Log.i(TAG, "Error: screenShot. ");e1.printStackTrace();}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic boolean onTouch(View v, MotionEvent event) {if (!playButtonVisible) {movie_play.setVisibility(View.VISIBLE);movie_play.setEnabled(true);} else {movie_play.setVisibility(View.INVISIBLE);}playButtonVisible = !playButtonVisible;return false;}@Overridepublic void onClick(View view) {switch (view.getId()) {case R.id.movie_videoview_play:if (!playPause) {movie_play.setText("Pause");} else {movie_play.setText("Play");}playPause = !playPause;int nowTime = videoView.getCurrentPosition();Log.i(TAG, "nowTime: " + nowTime);try {screenShot(videoView, nowTime);    //点击按钮截图,注意参数为videoView} catch (Exception e) {e.printStackTrace();}break;default:break;}}public void screenShot(View view, int nowTime) throws Exception {view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());view.setDrawingCacheEnabled(true);view.buildDrawingCache();Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());String path = Environment.getExternalStorageDirectory() + "/bitmap/" + nowTime + ".png";FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path);bitmap.compress(Bitmap.CompressFormat.PNG, 90, fileOutputStream);Log.i(TAG, "i: " + nowTime);} catch (Exception e) {Log.i(TAG, "Error: " + nowTime);e.printStackTrace();} finally {if (fileOutputStream != null) {fileOutputStream.close();}}bitmap.recycle();view.setDrawingCacheEnabled(false);}
}

DrawingCache 获取其他控件如Button等View时正常。

 

六.VideoView播放视频,MediaMetadataRetriever获取帧画面(就是你了!)

能够正常获取帧画面,并且画面之间不重复,即不是只取关键帧,而是去取相应时间点的帧画面。若不保存为图片(.png/.jpg),耗时最多为0.4s,基本达到要求。

参考:http://yashirocc.blog.sohu.com/175636801.html

public class MoviePlayerActivity extends Activity implements OnTouchListener, OnClickListener, Runnable {private static final String TAG = "ImageLight";private String file_path;private VideoView videoView;private Button movie_play;private boolean playButtonVisible;private boolean playPause;@Overrideprotected void onCreate(Bundle savedInstanceState) {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);    // full screenrequestWindowFeature(Window.FEATURE_NO_TITLE);                            // no titlesetRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);        // landscape or horizontal screensuper.onCreate(savedInstanceState);setContentView(R.layout.movie_player_activity);movie_play = (Button) findViewById(R.id.movie_videoview_play);movie_play.setOnClickListener(this);movie_play.setText("Play");Intent intent = getIntent();file_path = intent.getStringExtra("file_path");videoView = (VideoView) findViewById(R.id.movie_palyer_videoview);videoView.setMediaController(null);// videoView.setMediaController(new MediaController(this));
        videoView.setVideoPath(file_path);videoView.start();videoView.requestFocus();Thread screenShootThread = new Thread(this);screenShootThread.start();videoView.setOnTouchListener(this);}@Overridepublic void run() {MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();metadataRetriever.setDataSource(file_path);for (int i = 40000 * 1000; i < 50 * 1000 * 1000; i += 500 * 1000) {
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }            Bitmap bitmap = metadataRetriever.getFrameAtTime(videoView.getCurrentPosition()*1000, MediaMetadataRetriever.OPTION_CLOSEST);Log.i(TAG, "bitmap---i: " + i/1000);String path = Environment.getExternalStorageDirectory() + "/bitmap/" + i + ".png";FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(path);bitmap.compress(Bitmap.CompressFormat.PNG, 90, fileOutputStream);Log.i(TAG, "i: " + i/1000);} catch (Exception e) {Log.i(TAG, "Error: " + i/1000);e.printStackTrace();} finally {if (fileOutputStream != null) {try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}          bitmap.recycle();}}@Overridepublic boolean onTouch(View v, MotionEvent event) {if (!playButtonVisible) {movie_play.setVisibility(View.VISIBLE);movie_play.setEnabled(true);} else {movie_play.setVisibility(View.INVISIBLE);}playButtonVisible = !playButtonVisible;return false;}@Overridepublic void onClick(View view) {switch (view.getId()) {case R.id.movie_videoview_play:if (!playPause) {movie_play.setText("Pause");} else {movie_play.setText("Play");}playPause = !playPause;break;default:break;}}
}

这篇关于Android 播放视频并获取指定时间的帧画面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1047501

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk