视音频分离器,将一些格式的视频分离出视频轨道和音频轨道。
主要流程,也是主要的 API:
视音频合成器,将视频和音频合成相应的格式
以下是分离 mp4 音频和视频的源码及注释:
private String seperateMedia(String fileName, boolean isAudio) {
String type = isAudio ? "audio/" : "video/";
MediaExtractor mMediaExtractor = new MediaExtractor();;
MediaMuxer mMediaMuxer = null;
try {
mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 获取assert中的资源文件
AssetFileDescriptor fileDescriptor = getAssetFileSource();
// 设置资源文件
mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
int mMediaIndex = 0;
for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
//获取码流的详细格式/配置信息
MediaFormat format = mMediaExtractor.getTrackFormat(i);
String mine = format.getString(MediaFormat.KEY_MIME);
// 查找音频:"audio/" 或者视频:"video/"的轨道
if (mine.startsWith(type)) {
mMediaIndex = i;
break;
}
}
// 选择感兴趣的轨道
mMediaExtractor.selectTrack(mMediaIndex);
// 获取通道格式,可以自己新建,但是有坑
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);
int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);
// 当采集视频的使用,需要获取帧率,音频轨道没有这个参数
int framerate = isAudio ? 0 : mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
mMediaMuxer.start();
ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
int readSize = 0;
// writeSampleData需要BufferInfo参数
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
bufferInfo.size = readSize;
bufferInfo.flags = mMediaExtractor.getSampleFlags(); //设置为关键帧等
bufferInfo.offset = 0;
if (!isAudio) { // 时间戳,音频和视频的处理方式不一样
bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;
} else {
bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
}
mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);
Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );
mMediaExtractor.advance(); //下一帧
}
return "success";
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
if(mMediaExtractor != null ) {
mMediaExtractor.release();
mMediaExtractor = null;
}
if(mMediaMuxer != null) {
mMediaMuxer.stop();
mMediaMuxer.release();
mMediaMuxer = null;
}
}
return null;
}
bufferInfo 信息中需要写入正确的 presentationTimeUs,而看网上的参考基本都是 MediaExtractor.getSampleTime(),但在实际使用中会异常报错,也有使用通过计算前两个关键帧的差值,然后逐步增加,因为而且 presentationTimeUs 是需要递增的,但是实际上最后分离出来的视频模糊卡顿的现象。
在 Android 中如何提取和生成 mp4 文件 中看到根据帧率来计算 presentationTimeUs,效果比较正常,结合音频使用 mMediaExtractor.getSampleTime();
视频: int frameRate=mediaFormat=getInteger(MediaFormat.KEY_FRAME_RATE); //1s 的帧数
bufferInfo.presentationTimeUs = 1000 * 1000/frameRate; // 对于帧率为 x f/s 的视频而言,时间戳的间隔就是 1000/x ms
音频: bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
最近发现两个问题:
1. 为什么 getSampleTime() 不是逐步递增的?时间戳难道不是应该持续上升的吗?
2. 在某些机型上出现没有 MediaFormat.KEY_FRAME_RATE,该怎么计算 presentationTimeUs?
public class MediaExtractorActivity extends AppCompatActivity implements View.OnClickListener{
private static String TAG = MediaExtractorActivity.class.getSimpleName();
public static String[] STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mediaextractor);
findViewById(R.id.seperate_audio_btn).setOnClickListener(this);
findViewById(R.id.seperate_media_btn).setOnClickListener(this);
findViewById(R.id.muxer_btn).setOnClickListener(this);
}
@Override
public void onClick(View view) {
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, STORAGE, 2);
return;
}
switch (view.getId()) {
case R.id.seperate_audio_btn:
new MediaAsyncTask().execute(1);
break;
case R.id.seperate_media_btn:
new MediaAsyncTask().execute(2);
break;
case R.id.muxer_btn:
new MediaAsyncTask().execute(3);
break;
default:
break;
}
findViewById(R.id.seperate_audio_btn).setEnabled(false);
findViewById(R.id.seperate_media_btn).setEnabled(false);
findViewById(R.id.muxer_btn).setEnabled(false);
}
private class MediaAsyncTask extends AsyncTask<Integer, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(MediaExtractorActivity.this, "开始转化", Toast.LENGTH_SHORT).show();
}
@Override
protected String doInBackground(Integer... param) {
if (param.length < 1){
return null;
}
switch (param[0]) {
case 1:
return seperateMedia("audio.mp4", true);
case 2:
return seperateMedia("video.mp4", false);
case 3:
return muxerMediaAndAudio("video.mp4","audio.mp4", "result.mp4");
default:
return null;
}
}
@Override
protected void onPostExecute(String s) {
findViewById(R.id.seperate_audio_btn).setEnabled(true);
findViewById(R.id.seperate_media_btn).setEnabled(true);
findViewById(R.id.muxer_btn).setEnabled(true);
if (s != null) {
Toast.makeText(MediaExtractorActivity.this, "转化完成 " + s, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MediaExtractorActivity.this, "转化失败", Toast.LENGTH_SHORT).show();
}
}
}
private String muxerMediaAndAudio(String mediaFileName, String audioFileName, String resultName) {
File mediaFile = new File(getSDPath(), mediaFileName);
File audioFile = new File(getSDPath(), audioFileName);
if (!mediaFile.exists() || !audioFile.exists()) {
return "音视频文件不存在";
}
MediaMuxer mMediaMuxer = null;
MediaExtractor mMediaExtractor = new MediaExtractor();
MediaExtractor mAudioExtractor = new MediaExtractor();
try {
mMediaMuxer = new MediaMuxer(getSDPath() + "/" + resultName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mMediaExtractor.setDataSource(mediaFile.getAbsolutePath());
mAudioExtractor.setDataSource(audioFile.getAbsolutePath());
int mMediaTrack = 0;
int mAudioTrack = 0;
for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
String mine = mMediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);
if (mine.startsWith("video/")) {
mMediaTrack = i;
break;
}
}
for (int i = 0; i < mAudioExtractor.getTrackCount(); i++) {
String mine = mAudioExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);
if (mine.startsWith("audio/")) {
mAudioTrack = i;
break;
}
}
mMediaExtractor.selectTrack(mMediaTrack);
mAudioExtractor.selectTrack(mAudioTrack);
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaTrack);
MediaFormat audioFormat = mAudioExtractor.getTrackFormat(mAudioTrack);
MediaCodec.BufferInfo mediaBufferInfo = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
int mediaMuxerTrack = mMediaMuxer.addTrack(mediaFormat);
int audioMuxerTrack = mMediaMuxer.addTrack(audioFormat);
mMediaMuxer.start();
ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
int readSize = 0;
int mMediaFramerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
while ((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
mediaBufferInfo.presentationTimeUs += 1000 * 1000 / mMediaFramerate;
mediaBufferInfo.offset = 0;
mediaBufferInfo.flags = mMediaExtractor.getSampleFlags();
mediaBufferInfo.size = readSize;
mMediaMuxer.writeSampleData(mediaMuxerTrack, byteBuffer, mediaBufferInfo);
mMediaExtractor.advance();
}
while ((readSize = mAudioExtractor.readSampleData(byteBuffer, 0)) > 0) {
audioBufferInfo.presentationTimeUs = mAudioExtractor.getSampleTime();
audioBufferInfo.offset = 0;
audioBufferInfo.flags = mAudioExtractor.getSampleFlags();
audioBufferInfo.size = readSize;
mMediaMuxer.writeSampleData(audioMuxerTrack, byteBuffer, audioBufferInfo);
mAudioExtractor.advance();
}
mMediaMuxer.stop();
return getSDPath() + resultName;
} catch (IOException e) {
e.printStackTrace();
} finally {
if(mMediaExtractor != null ) {
mMediaExtractor.release();
mMediaExtractor = null;
}
if(mAudioExtractor != null ) {
mAudioExtractor.release();
mAudioExtractor = null;
}
if(mMediaMuxer != null) {
mMediaMuxer.release();
mMediaMuxer = null;
}
}
return null;
}
private String seperateMedia(String fileName, boolean isAudio) {
String type = isAudio ? "audio/" : "video/";
MediaExtractor mMediaExtractor = new MediaExtractor();;
MediaMuxer mMediaMuxer = null;
try {
mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 获取assert中的资源文件
AssetFileDescriptor fileDescriptor = getAssetFileSource();
// 设置资源文件
mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
int mMediaIndex = 0;
for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
//获取码流的详细格式/配置信息
MediaFormat format = mMediaExtractor.getTrackFormat(i);
String mine = format.getString(MediaFormat.KEY_MIME);
// 查找音频:"audio/" 或者视频:"video/"的轨道
if (mine.startsWith(type)) {
mMediaIndex = i;
break;
}
}
// 选择感兴趣的轨道
mMediaExtractor.selectTrack(mMediaIndex);
// 获取通道格式,可以自己新建,但是有坑
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);
int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);
// 当采集视频的使用,需要获取帧率,音频轨道没有这个参数
int framerate = 0;
if (mediaFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
framerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
}
mMediaMuxer.start();
ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
int readSize = 0;
// writeSampleData需要BufferInfo参数
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
bufferInfo.size = readSize;
bufferInfo.flags = mMediaExtractor.getSampleFlags(); //设置为关键帧等
bufferInfo.offset = 0;
if (framerate != 0) { // 时间戳,音频和视频的处理方式不一样
bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;
} else {
bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
}
mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);
Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );
mMediaExtractor.advance(); //下一帧
}
mMediaMuxer.stop();
return "success";
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
if(mMediaExtractor != null ) {
mMediaExtractor.release();
mMediaExtractor = null;
}
if(mMediaMuxer != null) {
mMediaMuxer.release();
mMediaMuxer = null;
}
}
return null;
}
private AssetFileDescriptor getAssetFileSource() throws IOException {
AssetManager manager = getAssets();
return manager.openFd("screen_recorder.mp4");
}
public static String getSDPath() {
// 判断是否挂载
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
return Environment.getRootDirectory().getAbsolutePath();
}
}
前言:找了很久,很少有NDK层使用合成mp4的例程,由于我们使用的是实时流合成mp4的文件,基本所有的视频流都是在C++操作的。因此写个笔记记录一下对实时流合成mp4格式视频。 1、要使用的对象: 2、创建对象:这里需要注意的是,一定要设置好视频流的vps/sps/pps数据。否则无法生生mp4文件。其中h265只需要使用csd_0。 3、写入数据:循环写入数据即可。这里写入的数据是h264/h2...
好时光 春分已过,下个时节就是清明了,送上古人小诗一首: 咏柳 【作者】贺知章 【朝代】唐 碧玉妆成一树高,万条垂下绿丝绦。 不知细叶谁裁出,二月春风似剪刀。 MediaExtractor 抽取视频不含音频 MediaExtractor 抽取音频不含视频 MediaExtractor 抽取视频音频文件,然后利用MediaMuxer合成 以上代码全部测试通过,欢迎交流!!!...
一、MediaExtractor API介绍 MediaExtractor的作用是把音频和视频的数据进行分离。 主要API介绍: setDataSource(String path):即可以设置本地文件又可以设置网络文件 getTrackCount():得到源文件通道数 getTrackFormat(int index):获取指定(index)的通道格式 getSampleTime()...
前言: 最近在做类似小咖秀的视频录制功能,也就是俗称的对嘴型表演,录制视频我用的是三方SDK,但是视频合成就需要自己搞了,在网上搜了挺多资料,国内国外网站看了不少,踩了很多坑,总算整出来了,在此分享给大家,希望对以后要做类似功能的兄弟们有所帮助! 需求: 将视频一的音频提取出来,视频二的视频图像提取出来,然后把它们合成新的视频。 工具准备: 视频的分离合成我主要用到了MediaExtractor和...
一.主要使用的方法 MediaExtractor 1.setDataSource(String path) 设置数据源 2.getTrackFormat(int index) 获取指定索引通道的数据配置参数 3.selectTrack(int index) 选择轨道 4. getSampleTime() 获取当前pts时间戳,获取不到这返回-1 5.readSampleData(@NonNull ...
项目中遇到一个问题,现象是录制视频时,录制一段时间后应用会crash。我们项目中是用MediaMuxer+MediaCodec来录制,最后生成一个MP4格式视频。 关键报错信息如下: 可以看到是native报错,直接去找项目源码(https://android.googlesource.com/platform/frameworks/av/ 这里面可以看到framework的代码): 发现并没有相...
http://www.jianshu.com/p/aeadf260258a 在Android中,可以使用MediaMuxer来封装编码后的视频流和音频流到mp4容器中: MediaMuxer facilitates muxing elementary streams. Currently supports mp4 or webm file as the output and at most one...
目录 MediaExtractor MediaMuxer 能做什么 视频解封装和合成的API以及流程介绍 三个实践(视频解封装提取纯音轨和视频轨文件、再合成新视频、给视频换个背景音) 遇到的问题 收获 一、有什么实际应用 在我们日常使用短视频软件的时候,对视频的裁剪,拼凑,加入背景是很常用的操作,这些功能是如何实现的呐?其实是将视频多信道的分离出来,比如音轨和视频轨道分隔出来,可以做到二次合成。 ...
代码涉及音频编码,视频编码,pts计算,可参考前面的几篇博文,已经模块化 这里pts计算直接使用outputStream的时间基,不存在inputStream转outputStream时间基的问题。因为yuv和pcm都是裸数据,no pts。所以直接写pts值就行。如果是从aac和h264合成mp4那么就需要做输入输出流的时间基转换了。关于时间基的理解和转换,参考上篇深入理解pts,dts,tim...
MediaExtractor与MediaMuxer工具可以完美的将视频中的音视频分开来,可以对视频进行再配音合成达到自己的需求: 1.准备工作:声明基础变量 2.开始分离音视频: 该方法接受视频路径与所要分离出的媒体文件格式:audio或者video,根据媒体格式进行操作。 3.分离视频: 4.分离出音频: 5.释放资源: 转载于:https://my.oschina.net/fangfeiAI/...