首页  编辑  

安卓视频加水印,录像,并保存MP4文件

Tags: /Android/   Date Created:
核心代码:

启动视频录像
    public boolean startVideo(int id, String fileName, int width, int height, String osd, boolean rotate) {
lock.lock();
Log.i(TAG, String.format("开始录像: 摄像头=%d, %s, 尺寸=%d×%d, OSD: %s", id, fileName, width, height, osd));
try {
closeCamera(); // 防止各种意外情况下,没有关闭摄像头
cameraOpenned = false;
isRunning = true;
this.scale = scale > 0 ? scale : 20;
this.fileName = fileName;
this.idCamera = id;
this.rotate = rotate;
this.width = width;
this.height = height;
this.osd = osd;
if (id > Camera.getNumberOfCameras() - 1) {
Log.w(TAG, "摄像头ID大于摄像头个数");
return false;
}

// 初始化编码器并录像
mediaCodec = MediaCodec.createEncoderByType("Video/AVC");
mediaMuxer = new MediaMuxer(fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
MediaFormat mediaFormat = MediaFormat.createVideoFormat("Video/AVC", width, height);
int framerate = 15;
int bitrate = 1000 * 1000 * 2; // 2Mbps码率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
// 因为我们要加水印,所以用位图了,而位图是默认的ARGB_8888彩色格式,所以编码器用这个格式,需要手机支持!
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_Format32bitARGB8888);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //关键帧间隔时间 单位s
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();

mCamera = Camera.open(id);
mCamera.setPreviewTexture(mTexture);
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(width, height);
parameters.setPreviewFormat(ImageFormat.NV21);
mCamera.setParameters(parameters);
mCamera.setPreviewCallback(this);
mCamera.startPreview();
return mCamera != null;
} catch (Exception e) {
Log.e(TAG, "录像失败: " + e.getMessage());
return false;
} finally {
lock.unlock();
}
}

public void stopVideo() {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mediaMuxer.stop();
mediaMuxer.release();
mediaCodec.stop();
mediaCodec.release();
}

添加水印
private void drawWatermark(Bitmap bitmap) {
Canvas canvas = new Canvas(bitmap);
String time = lyh.Utils.formatDateTime("yyyy-MM-dd HH:mm:ss EEE", new Date());
final int w = bitmap.getWidth();
final int h = bitmap.getHeight();
long x, y;

// 计算单个字符的高度和宽度,以便后面添加水印的时候计算字符的位置
paint.setTextSize(w / scale);

Rect bounds = new Rect();
paint.getTextBounds(osd, 0, osd.length(), bounds);
fontWidth = (int) paint.getTextSize() * osd.length();
fontHeight = bounds.height();

// 顶部时间戳,X, Y 总是固定从左上角 1 / 20 处开始绘制
x = w / 30;
y = x + fontHeight / 2;
canvas.drawText(time, x, y, paint);

// 右下角OSD文本,固定右下角边距为 1 / 20 比例
x = w - fontWidth;
y = h - w / 30;
canvas.drawText(osd, x, y, paint);

// 右上角,绘制信号强度和流量
long totalBytes = TrafficStats.getMobileRxBytes() + TrafficStats.getMobileTxBytes();
String traffic = Utils.humanReadableByteCount(totalBytes, true);
int level = getRadioSignalLevel();
if (level < 0) level = 0;
if (level > 4) level = 4;
String singnal = SIGNAL_LEVELS[level];
String osdRightTop = String.format("流量: %s 信号: %s", traffic, singnal);
paint.setTextSize(w / 60);
x = w - (int) paint.getTextSize() * (osdRightTop.length() - 5);
y = w / 30 + fontHeight / 2;
canvas.drawText(osdRightTop, x, y, paint);
}

@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
// 从回调数据解码 YUV 图像,并转成JPG
Camera.Size previewSize = camera.getParameters().getPreviewSize();
YuvImage image = new YuvImage(bytes, ImageFormat.NV21, previewSize.width, previewSize.height, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, stream);

// 从 JPG 转码位图
Bitmap bitmap;
if (rotate) {
Bitmap raw = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size(), bitmapOption);
Matrix matrix = new Matrix();
matrix.postRotate(270);
bitmap = Bitmap.createBitmap(raw, 0, 0, raw.getWidth(), raw.getHeight(), matrix, false);
raw.recycle();
} else {
bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size(), bitmapOption);
}

drawWatermark(bitmap);

Canvas canvasSV = surfaceView.getHolder().lockCanvas();
if (canvasSV != null) {
Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect dst = new Rect(0, 0, canvasSV.getWidth(), canvasSV.getHeight());
canvasSV.drawBitmap(bitmap, src, dst, null);
surfaceView.getHolder().unlockCanvasAndPost(canvasSV);
}

// 压缩编码并存储文件
ByteBuffer buf = ByteBuffer.allocate(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(buf);
byte[] yuvBytes = buf.array();

ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(5000000);
if (inputBufferIndex >= 0) {//当输入缓冲区有效时,就是>=0
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.put(yuvBytes);
//往输入缓冲区写入数据, 五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是
mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuvBytes.length, System.nanoTime() / 1000, 0);
}

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);//拿到输出缓冲区的索引
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat mediaFormat = mediaCodec.getOutputFormat();
mTrackIndex = mediaMuxer.addTrack(mediaFormat);
mediaMuxer.start();
} else while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
//outData就是输出的h264数据
// outputStream.write(outData, 0, outData.length);//将输出的h264数据保存为文件,用vlc就可以播放
mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}

bitmap.recycle();
System.gc();
}

Android使用系统API进行音视频编码 - 程序园.mhtml (690.9KB)