有了上文的基础,我们可以将解析出视频流所需要的Box提取出来,进行我们的操作。下面的UML图详细地描述了我们编写程序的过程。
类图
MP4中所有数据结构都是Box,每个box定义三个共同的熟悉startoffset、size、type。startoffset是box相对于文件头的偏移量,size是box的长度,type是box的类型。这里利用java的RandomAccessFile类封装MP4文件方便我们处理。先遍历文件,得到MoovBox,再一层层取得下层子Box直到StblBox,再得到我们最后所需要的Stco等Box。先以chunk为单位将视频原始码流数据从mdat中取出存入数组,再以sample为单位加StartCode以及写入文件,在IDR帧前加上sps NALU 和pps NALU。编程的过程比较简单,拆包,取值,读写数据。代码略过了很多步骤,不过能满足解封装出H.264码流的基本功能。典型代码如下:
package com.example.l.objectlib6;
import java.io.IOException;
import java.io.RandomAccessFile;
public class MinfBox extends Box {
public MinfBox(int startoffset,int size,String type){
super(startoffset,size,type);
}
public StblBox getStblBox(RandomAccessFile raf) throws IOException{
raf.seek(this.getStartoffset());
raf.skipBytes(8);
int len1 = raf.readInt();
raf.skipBytes(len1-4);
int len2 = raf.readInt();
raf.skipBytes(len2-4);
int stblsize = raf.readInt();
int stbloffset = this.getStartoffset() + len1 + len2 + 8;
StblBox stblBox = new StblBox(stbloffset,stblsize,"stbl");
return stblBox;
}
}
package com.example.l.objectlib6;
import java.io.IOException;
import java.io.RandomAccessFile;
public class StcoBox extends Box {
public StcoBox(int startoffset, int size, String type) {
super(startoffset, size, type);
}
private int chunknum;
private int[] chunkoffset;
//获取chunk的数目
public int getChunknum(RandomAccessFile raf) throws IOException {
raf.seek(this.getStartoffset() + 15);
chunknum = raf.readUnsignedByte();
return chunknum;
}
//获取chunk的偏移量数组
public int[] getChunkOffset(RandomAccessFile raf,int chunknum) throws IOException {
chunkoffset = new int[chunknum];
raf.seek(this.getStartoffset() + 16);
for (int i=0;i<chunknum;i++){
chunkoffset[i] = raf.readInt();
}
return chunkoffset;
}
package com.example.l.objectlib6;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class MainClass {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = null;
try {
System.out.println("请输入MP4文件的路径:");
Scanner scanner = new Scanner(System.in);
String path = scanner.nextLine();
raf = new RandomAccessFile(path,"r");
MoovBox moovBox = GetMoovClass.getMoovBox(raf);
TrakBox trakBox = moovBox.getTrakBox(raf);
MdiaBox mdiaBox = trakBox.getMdiaClass(raf);
MinfBox minfBox = mdiaBox.getMinfBox(raf);
StblBox stblBox = minfBox.getStblBox(raf);
StcoBox stcoBox = stblBox.getStcoBox(raf);
StscBox stscBox = stblBox.getStscBox(raf);
StsdBox stsdBox = stblBox.getStsdBox(raf);
StssBox stssBox = stblBox.getStssBox(raf);
StszBox stszBox = stblBox.getStszBox(raf);
//1.调用方法得到chunknumber chunkoffset[],samplenumber, 通过sampleperchunk[] sizepersample得到chunksize[]进而从mdat中取得原始视频数据
int chunknumber = stcoBox.getChunknum(raf);
int[] chunkoffset = stcoBox.getChunkOffset(raf,chunknumber);
int[] sampleperchunk = stscBox.getSamplePerChunk(raf,chunknumber);
int samplenumber = stszBox.getSampleNum(raf);
int[] sizepersample = stszBox.getSizePerSample(raf,samplenumber);
int[] sizeperchunk = new int[chunknumber];
int mdatlen = 0;
int num = 0;
for (int i=0;i<chunknumber;i++){
for (int j=0;j<sampleperchunk[i];j++){
sizeperchunk[i] += sizepersample[num+j];
}
num += sampleperchunk[i];
mdatlen += sizeperchunk[i];
}
byte[] mdat = new byte[mdatlen];
int k = 0;
for (int i=0;i<chunknumber;i++){
byte[] bData = new byte[sizeperchunk[i]];
raf.seek(chunkoffset[i]);
raf.read(bData);
System.arraycopy(bData,0,mdat,k,sizeperchunk[i]);
k += sizeperchunk[i];
}
//2.通过sizepernumber[],得到sampleoffset[],利用此数组 将原始数据的每一帧的前四个数据替换为00000001
mdat[0] = 00;
mdat[1] = 00;
mdat[2] = 00;
mdat[3] = 01;
int[] sampleoffset = new int[samplenumber];
int m = 0;
for (int i=0;i<samplenumber - 1;i++) {
sampleoffset[i] = m;
m += sizepersample[i];
}
for (int i=0;i<samplenumber -1;i++){
mdat[sampleoffset[i]] = 00;
mdat[sampleoffset[i]+1] = 00;
mdat[sampleoffset[i]+2] = 00;
mdat[sampleoffset[i]+3] = 01;
}
//3.调用方法得到IDRnumber IDRSample[] sps[] pps[],在每个IDRSample之前加上00000001sps[]00000001pps[]写入文件
int IDRnumber = stssBox.getIDRnum(raf);
int[] IDRsample = stssBox.getIDRSample(raf,IDRnumber);
int spslen = stsdBox.getSpslen(raf);
int ppslen = stsdBox.getPpslen(raf,spslen);
byte[] sps = stsdBox.getSps(raf,spslen);
byte[] pps = stsdBox.getPps(raf,spslen,ppslen);
byte[] bt1 = new byte[4];
bt1[0] = 00;
bt1[1] = 00;
bt1[2] = 00;
bt1[3] = 01;
System.out.println("请输入视频流的路径:");
Scanner scanner1 = new Scanner(System.in);
String filepath = scanner1.nextLine();
File file = new File(filepath);
FileOutputStream fos = new FileOutputStream(file);
for (int i=1;i<IDRnumber;i++){
fos.write(bt1);
fos.write(sps);
fos.write(bt1);
fos.write(pps);
fos.write(mdat,sampleoffset[IDRsample[i-1]],sampleoffset[IDRsample[i]]-sampleoffset[IDRsample[i-1]]);
}
fos.write(bt1);
fos.write(sps);
fos.write(bt1);
fos.write(pps);
fos.write(mdat,sampleoffset[IDRsample[IDRnumber-1]],mdatlen-sampleoffset[IDRsample[IDRnumber-1]]);
System.out.println("success");
} catch (FileNotFoundException e) {
System.out.println("file not found");
}
}
}
本人编程能力比较入门,程序有很多可优化之处,欢迎指教。