需求是只拍照视频播放位置
/** 拍照视频播放位置* */
public static void getBitmap(Context context, TextureView vv){
public static String BASE_URL = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/";
String mPath = Path.BASE_URL + System.currentTimeMillis() + ".jpg";
Toast.makeText(context, "图片已保存到" + mPath, Toast.LENGTH_SHORT).show(); Bitmap bm = vv.getBitmap();
if(bm == null)
Log.e(TAG,"bitmap is null");
OutputStream fout = null;
File imageFile = new File(mPath);
try {
fout = new FileOutputStream(imageFile); bm.compress(Bitmap.CompressFormat.JPEG, 90, fout);
fout.flush();
fout.close();
} catch (FileNotFoundException e) {
Log.e(TAG, "FileNotFoundException"); e.printStackTrace();
} catch (IOException e) {
Log.e(TAG, "IOException"); e.printStackTrace(); }
//通知系統相冊刷新保持的錄屏,否则图库不会更新
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(new File(mPath))); context.sendBroadcast(intent);
}
由于录制视频需求是只录制展示视频的那个区域,实现了mediaRecorder录制全屏,mediaRecorder.setVideoSize(width, height);可以设置从屏幕左上角开始录制的宽高,但是状态栏也会录上,ffmpeg、OpenGL都可以实现,本人对底层不懂,视频功能我是用了图片合成MP4文件
implementation 'org.jcodec:jcodec-android:0.1.9'//图片合成视频
最终使用这个框架实现多张图片合成MP4视频
生成视频的图片和拍照的图片代码一样,只是换了不同目录
public static void Image2Mp4(Context context ,String videoName){
try {
Log.e("performJcodec: ","执行开始");
SequenceEncoderMp4 se =null;
File file =new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() +"/wy/" +"pic/");
File fileout =new File(Path.BASE_URL);
if (!fileout.exists()) {
fileout.mkdir();
}
File out =new File(Path.BASE_URL, videoName +".mp4");
se =new SequenceEncoderMp4(out);
File[]files =file.listFiles();
for (int i =0; i
if (!files[i].exists()) {
break;
}
Bitmap frame =BitmapUtil.decodeSampledBitmapFromFile(files[i].getAbsolutePath() ,480 ,320);
se.encodeImage(frame);
Log.e("performJcodec: ","执行到的图片是 " + i);
}
se.finish();
Log.e("performJcodec: ","执行完成"+videoName+".mp4");
//发送广播通知挂载文件
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(out)));
}catch (IOException e) {
Log.e("performJcodec: ","执行异常 " + e.toString());
}
//删除sdcard创建的临时文件
File imageFile =new File(Path.BASE_URL +"pic/");
PictureUtils.delete(imageFile);
}
public static void delete(File file) {
if (file.isDirectory()) {
File[]childFiles = file.listFiles();
//空文件夹直接删除
if (childFiles ==null ||childFiles.length ==0) {
file.delete();
return;
}
//非空循环删除
for (int i =0; i
deleteFile(childFiles[i]);
}
file.delete();
}
}
public static void deleteFile(File file) {
if (file.isFile()) {
file.delete();
return;
}
}
还有几个重要的类
public class SequenceEncoderMp4
extends org.jcodec.api.android.SequenceEncoder {
/**
* 控制帧率缩放,时间长度调整这个值
* 值越小,生成视频时间越长
* 如:timeScale = 50 一秒视屏采用50张图片
*/
private int timeScale =10;
public SequenceEncoderMp4(File out)
throws IOException
{
super(out);
this.ch =NIOUtils.writableFileChannel(out);
// Muxer that will store the encoded frames
muxer =new MP4Muxer(ch,Brand.MP4);
// Add video track to muxer
outTrack =muxer.addTrack(TrackType.VIDEO,timeScale);
// Allocate a buffer big enough to hold output frames
_out =ByteBuffer.allocate(1920 *1080 *6);
// Create an instance of encoder
encoder =new H264Encoder();
// Transform to convert between RGB and YUV
transform =ColorUtil.getTransform(ColorSpace.RGB,encoder.getSupportedColorSpaces()[0]);
// Encoder extra data ( SPS, PPS ) to be stored in a special place of
// MP4
spsList =new ArrayList();
ppsList =new ArrayList();
}
private SeekableByteChannel ch;
private Picture toEncode;
private Transform transform;
private H264Encoder encoder;
private ArrayListspsList;
private ArrayListppsList;
private FramesMP4MuxerTrack outTrack;
private ByteBuffer _out;
private int frameNo;
private MP4Muxer muxer;
public void encodeNativeFrame(Picture pic)throws IOException {
if (toEncode ==null) {
toEncode =Picture.create(pic.getWidth() , pic.getHeight() ,encoder.getSupportedColorSpaces()[0]);
}
// Perform conversion
transform.transform(pic,toEncode);
// Encode image into H.264 frame, the result is stored in '_out' buffer
_out.clear();
ByteBuffer result =encoder.encodeFrame(toEncode,_out);
// Based on the frame above form correct MP4 packet
spsList.clear();
ppsList.clear();
H264Utils.wipePS(result,spsList,ppsList);
H264Utils.encodeMOVPacket(result);
// Add packet to video track
outTrack.addFrame(new MP4Packet(result,frameNo,timeScale,1,frameNo,true,null,frameNo,0));
frameNo++;
}
public void finish()throws IOException {
// Push saved SPS/PPS to a special storage in MP4
outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList,ppsList,4));
// Write MP4 header and finalize recording
muxer.writeHeader();
NIOUtils.closeQuietly(ch);
}
}
public class BitmapUtil {
public static void checkArgs(String filePath){
BitmapFactory.Options options =new BitmapFactory.Options();
options.inJustDecodeBounds =true;
BitmapFactory.decodeFile(filePath,options);
int imageHeight =options.outHeight;
int imageWidth =options.outWidth;
String imageType =options.outMimeType;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth,int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize =1;
if (height > reqHeight ||width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio =Math.round((float)height / (float) reqHeight);
final int widthRatio =Math.round((float)width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize =heightRatio
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromFile(String filePath,
int reqWidth,int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options =new BitmapFactory.Options();
options.inJustDecodeBounds =true;
BitmapFactory.decodeFile(filePath,options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize =calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeFile(filePath,options);
}
}
参考这个GitHub https://github.com/ynztlxdeai/ImageToVideo
网友评论