没有合适的资源?快使用搜索试试~ 我知道了~
首页Android视频处理之动态时间水印效果
Android视频处理之动态时间水印效果
895 浏览量
更新于2023-05-24
评论 3
收藏 193KB PDF 举报
主要A为大家详细介绍了Android视频处理之动态时间水印效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
资源详情
资源评论
资源推荐

Android视频处理之动态时间水印效果视频处理之动态时间水印效果
主要A为大家详细介绍了Android视频处理之动态时间水印效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
最近的项目中遇到一个非常头痛的需求,在Android端录制视频的时候动态添加像监控画面一样的精确到秒的时间信息,关键是,并不是说只在播放器的界
面显示时间就可以了,而是录制到视频里面去,这个MP4在电脑上播放也能看到每个画面的时间。
最后想到的办法是在录制完成以后去处理这个视频。
期间参考了很多资料,比较有用的大概是ffmpeg和比较新的Api mediaCodec系列了。介于ffmpeg都是C实现,和一大堆NDK相关,本人不是太懂,就重点
关注了MediaCodec系列。
参考逻辑流程图一目了然的这篇博文
MediaCodec进行编解码的大体逻辑是这样的(转载):
主要函数的调用逻辑如下:
MediaExtractor,MediaCodec,MediaMuxer这三个Api已经可以很多多媒体处理工作了,比如用MediaExtractor+MediaMuxer就可以做音视频剪
辑,MediaCodec+MediaMuxer就可以做自定义的录像机,一起用就可以做特效编辑,滤镜之类的了。
添加时间水印效果
关键在于取到的数据帧,是YUV格式的,根据拍摄时选取的不同还不一样,我用到的NV21格式,也就是YUV420sp,拿到NV21格式的帧以后,转成RGB
渲染,然后又转回NV21交给encoder,看起来好笨重,也非常地耗时,但我还没找到更好的办法。
private Bitmap first;

private void handleFrameData(byte[] data, MediaCodec.BufferInfo info) {
//YUV420sp转RGB数据 5-60ms
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, srcWidth, srcHeight, null);
yuvImage.compressToJpeg(new Rect(0, 0, srcWidth, srcHeight), 100, out);
byte[] imageBytes = out.toByteArray();
//旋转图像,顺便解决电脑上播放被旋转90度的问题 20-50ms
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
Bitmap bitmap = rotaingImageView(videoRotation, image);
image.recycle();
//渲染文字 0-1ms
Canvas canvas = new Canvas(bitmap);
canvas.drawText(videoTimeFormat.format(videoFirstTime + info.presentationTimeUs / 1000), 10, 30, paint);
//预览处理帧 0-5ms
first = bitmap;
handler.sendEmptyMessage((int) (info.presentationTimeUs / 1000));
synchronized (MediaCodec.class) {//记得加锁
timeDataContainer.add(new Frame(info, bitmap));
}
}
/*
* 旋转图片
* @param angle
* @param bitmap
* @return Bitmap
*/
public Bitmap rotaingImageView(int angle, Bitmap bitmap) {
//旋转图片 动作
Matrix matrix = new Matrix();
matrix.postRotate(angle);
// 创建新的图片
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
然后是转回NV21
/**
* 获取夹了时间戳的的数据
*
* @return
*/
private Frame getFrameData() {
synchronized (MediaCodec.class) {//记得加锁
if (timeDataContainer.isEmpty()) {
return null;
}
//从队列中获取数据
Frame frame = timeDataContainer.remove(0);////取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存
//转回YUV420sp 120-160ms
frame.data = getNV21(dstWidth, dstHeight, frame.bitmap);
return frame;
}
}
public static byte[] getNV21(int width, int height, Bitmap scaled) {
int[] argb = new int[width * height];
scaled.getPixels(argb, 0, width, 0, 0, width, height);
byte[] yuv = new byte[width * height * 3 / 2];
encodeYUV420SP(yuv, argb, width, height);
scaled.recycle();
return yuv;
}
/**
* 将bitmap里得到的argb数据转成yuv420sp格式
* 这个yuv420sp数据就可以直接传给MediaCodec,通过AvcEncoder间接进行编码
*
* @param yuv420sp 用来存放yuv420sp数据
* @param argb 传入argb数据
* @param width 图片width
* @param height 图片height
*/
public static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
// a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
R = (argb[index] & 0xff0000) >> 16;

G = (argb[index] & 0xff00) >> 8;
B = (argb[index] & 0xff) >> 0;
// well known RGB to YUV algorithm
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
// NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
// pixel AND every other scanline.
yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
}
index++;
}
}
}
看到上面的代码执行耗时,根本不可能实时录制时处理,就算后台服务处理,3秒钟的720*480视频得花费约20秒..
解码与编码的地方也有很多,比如编码器在某些手机不支持颜色格式,为了适配更多机型,颜色格式的设置以后需要更换。
/**
* 初始化编码器
*/
private void initMediaEncode(String mime) {
try {
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, dstWidth, dstHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE, 1024 * 512);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 27);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
// format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mediaEncode = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
mediaEncode.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException e) {
e.printStackTrace();
}
if (mediaEncode == null) {
JLog.e(tag, "create mediaEncode failed");
return;
}
mediaEncode.start();
}
补充:补充:匹配大部分手机的颜色模式应该是MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar=21,这个颜色格式是在decode解码首
buffer的时候得到的,但这个format居然没有码率,关键帧间隔,以及FPS等,这些只能根据自己情况设
为什么我之前用了YUV420Flexible,android源码里面说的YUV420SemiPlanner弃用
@deprecated Use {@link #COLOR_FormatYUV420Flexible}。
public static final int COLOR_FormatYUV420SemiPlanar = 21;
不过现在可以从源文件首buffer里面解码读取出来
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat format = mediaDecode.getOutputFormat();
Log.d(tag, "New format " + format);
if (format != null && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
videoColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Log.d(tag, "decode extract get videoColorFormat =" + videoColorFormat);//解码得到视频颜色格式
}
initMediaEncode(videoColorFormat);//根据颜色格式初始化编码器
break;
源码:
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;

import android.os.Message;
import android.support.annotation.Nullable;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* Created by user on 2016/8/13.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class TestCodecService extends Service {
private MediaExtractor extractor;
private MediaMuxer muxer;
private final static String tag = "px";
private final String TAG = this.getClass().getSimpleName();
private MediaFormat format;
private int videoMaxInputSize = 0, videoRotation = 0;
private long videoDuration;
private boolean decodeOver = false, encoding = false, mCancel, mDelete;
//视频流在数据流中的序号
private int videoTrackIndex = -1;
private MediaCodec mediaDecode, mediaEncode;
private ByteBuffer[] decodeInputBuffers, decodeOutputBuffers;
private ArrayList<Frame> timeDataContainer;//数据块容器
private MediaCodec.BufferInfo decodeBufferInfo;
private int srcWidth, srcHeight, dstWidth, dstHeight;
private SimpleDateFormat videoTimeFormat;
private int mProgress, mMax;
private VideoCodecDao codecDao;
//绘制时间戳的画笔
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
public void onCreate() {
super.onCreate();
JLog.d(TAG, "onCreate");
//视频时间戳显示格式
videoTimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
timeDataContainer = new ArrayList<>();
//初始化画笔工具
paint.setColor(Color.WHITE);
paint.setTextSize(20);
codecDao = VideoCodecDao.getInstance(JingRuiApp.getJRApplicationContext());
}
@Override
public void onDestroy() {
super.onDestroy();
JLog.d(TAG, "onDestroy");
decodeOver = true;
encoding = false;
}
private void init(String srcPath, String dstpath) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(srcPath);
try {
srcWidth = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
srcHeight = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
try {
剩余17页未读,继续阅读
















安全验证
文档复制为VIP权益,开通VIP直接复制

评论0