Skip to content

Commit

Permalink
* 改写FLV文件duration字段
Browse files Browse the repository at this point in the history
* 修复Non-monotonous DTS in output stream问题
* 修改userName的正则匹配
  • Loading branch information
nICEnnnnnnnLee committed May 17, 2019
1 parent 46656d6 commit b8eb544
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 122 deletions.
Binary file modified release/BiliLiveRecorder.jar
Binary file not shown.
23 changes: 17 additions & 6 deletions src/nicelee/bilibili/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;

import nicelee.bilibili.enums.StatusEnum;
import nicelee.bilibili.live.FlvChecker;
import nicelee.bilibili.live.RoomDealer;
import nicelee.bilibili.live.domain.RoomInfo;
Expand All @@ -15,13 +16,14 @@
public class Main {

public static void main(String[] args) throws IOException {
//args = new String[]{"debug"};
if(args != null && args.length >= 1) {
Logger.debug = true;
}else {
Logger.debug = false;
}
// 等待输入房间号
System.out.println("bilibili 直播录制 version v1.1");
System.out.println("bilibili 直播录制 version v1.2");
System.out.println("请输入房间号(直播网址是https://live.bilibili.com/xxx,那么房间号就是xxx)");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
long shortId;
Expand Down Expand Up @@ -55,7 +57,10 @@ public static void main(String[] args) throws IOException {
public void run() {
System.out.println("开始录制,输入stop停止录制");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH.mm");
String filename = roomInfo.getUserName().replaceAll("[\\\\|\\/|:\\*\\?|<|>|\\||\\\"$]", ".") + "的直播" + sdf.format(new Date()) + ".flv";
String filename = String.format("%s-%d 的直播 %s.flv",
roomInfo.getUserName().replaceAll("[\\\\|\\/|:\\*\\?|<|>|\\||\\\"$]", "."),
roomInfo.getShortId(),
sdf.format(new Date()));
roomDealer.startRecord(url, filename, roomInfo.getShortId());
// 将后缀.part去掉
File file = roomDealer.util.getFileDownload();
Expand Down Expand Up @@ -86,18 +91,24 @@ public void run() {
Thread.sleep(10000); // 每10s汇报一次情况
} catch (InterruptedException e) {
}
int period = (int) ((System.currentTimeMillis() - beginTime) / 1000);
System.out.print("已经录制了 " + period);
System.out.println("s, 当前进度: " + RoomDealer.transToSizeStr(roomDealer.util.getDownloadedFileSize()));
if(roomDealer.util.getStatus() == StatusEnum.DOWNLOADING) {
int period = (int) ((System.currentTimeMillis() - beginTime) / 1000);
System.out.print("已经录制了 " + period);
System.out.println("s, 当前进度: " + RoomDealer.transToSizeStr(roomDealer.util.getDownloadedFileSize()));
}else {
System.out.print("正在处理时间戳,请稍等 ");
}
}
}
}, "thread-monitoring").start();
String line;
while ((line = reader.readLine()) != null) {
if (line.equals("stop")) {
if (line.startsWith("stop") || line.startsWith("q")) {
roomDealer.stopRecord();
reader.close();
break;
}else {
System.out.println("输入stop 或 q 停止录制");
}
}
}
Expand Down
269 changes: 156 additions & 113 deletions src/nicelee/bilibili/live/FlvChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class FlvChecker {


public static void main(String[] args) throws IOException {
if(args != null) {
if(args != null && args.length>=1) {
System.out.println("校对时间戳开始...");
check(args[0]);
System.out.println("校对时间戳完毕。");
Expand All @@ -29,19 +29,20 @@ public static void main(String[] args) throws IOException {
* @param path
* @throws IOException
*/
private static int duration;
// 用于统计时间戳
private static int firstTimeStamp[] = {-1, -1, -1}, lastTimestamp[] = {-1, -1, -1}, cnt[] = {0, 0, 0};
public static void check(String path) throws IOException {
File file = new File(path);
RandomAccessFile raf = new RandomAccessFile(file, "rw");

// 跳过头部
raf.skipBytes(9);

// 用于统计时间戳
int firstTimeStamp = -1, lastTimestamp = -1;
// 用于排除无效尾巴帧
long currentLength = 9L, latsValidLength = currentLength;
try {
while (true) {
//int remain = 20;
while (true) {//&& remain>=0
//remain--;
// 读取前一个tag size
int predataSize = readBytesToInt(raf, 4);
latsValidLength = currentLength; currentLength = raf.getFilePointer();
Expand All @@ -64,42 +65,85 @@ public static void check(String path) throws IOException {
// tag data size 3个字节。表示tag data的长度。从streamd id 后算起。
int dataSize = readBytesToInt(raf, 3);
Logger.print(" ,当前tag data 长度为:" + dataSize);
// 时间戳 3+1
// 时间戳 3
int timestamp = readBytesToInt(raf, 3);
if (firstTimeStamp <= 0) {
firstTimeStamp = timestamp;
raf.seek(raf.getFilePointer() - 3);
byte[] zeros = { 0, 0, 0 };
raf.write(zeros, 0, 3);
} else {
// 登记有效时间戳
if (timestamp > 0) {
lastTimestamp = timestamp;
}
// 修改当前时间戳
int currentTime = lastTimestamp - firstTimeStamp;
raf.seek(raf.getFilePointer() - 3);
raf.write(int2Bytes(currentTime), 1, 3);
// 回滚时间戳
raf.seek(raf.getFilePointer() - 3);
// 分情况讨论时间戳
switch(tagType) {
case 8 :// audio
dealTimestamp(raf, timestamp, 0);
break;
case 9 :// video
dealTimestamp(raf, timestamp, 1);
break;
case 18: //scripts
dealTimestamp(raf, timestamp, 2);
break;
}

Logger.println(" ,当前timestamps 为:" + timestamp);
raf.skipBytes(1);
// stream id 3个字节。总是0
raf.skipBytes(3);
// timestampEx 扩展保留 1个字节 + stream id 3个字节。总是0
// 跳过数据
raf.skipBytes(dataSize);
raf.skipBytes(4 + dataSize);
}
} catch (Exception e) {
e.printStackTrace();
}
Logger.println();
Logger.println("firstTimeStamp 为:" + firstTimeStamp);
Logger.println("lastTimestamp 为:" + lastTimestamp);
Logger.println("duration 为:" + (lastTimestamp - firstTimeStamp));
duration = lastTimestamp[1] - firstTimeStamp[1];
Logger.println("firstTimeStamp 为:" + firstTimeStamp[1]);
Logger.println("lastTimestamp 为:" + lastTimestamp[1]);
Logger.println("duration 为:" + duration);
Logger.println("currentLength 为:" + currentLength);
raf.close();
double dura = duration;
dura = dura / 1000;
changeDuration(path, dura);
}

/**
* 处理音频时间戳
* @param raf
* @param timestamp
* @throws IOException
*/
private static void dealTimestamp(RandomAccessFile raf, int timestamp, int type) throws IOException {
// 尚未找到非0 起始时间戳, 此时,一直递增
if (firstTimeStamp[type] < 0) {
firstTimeStamp[type] = timestamp;
lastTimestamp[type] = timestamp;
byte[] counts = { 0, 0, (byte) cnt[type] };
raf.write(counts, 0, 3);
cnt[type]++;
Logger.print(" ,修改前timestamps 为:" + timestamp);
Logger.print(" ,当前timestamps 为:" + cnt[type]);
Logger.println();
}else if(timestamp - lastTimestamp[type] > 666) {
// 登记有效时间戳
lastTimestamp[type] = timestamp;
// 如果中间间隔太大,那么认为起始值不对,仍然需要调整
cnt[type]++;
byte[] counts = { 0, 0, (byte) cnt[type] };
raf.write(counts, 0, 3);
firstTimeStamp[type] = lastTimestamp[type] - cnt[type];
Logger.print(" ,修改前timestamps 为:" + timestamp);
Logger.print(" ,当前timestamps 为:" + cnt[type]);
Logger.println();
}else {
// 登记有效时间戳
if (timestamp > lastTimestamp[type] ) {
lastTimestamp[type] = timestamp;
}else { // 必须确保递增
lastTimestamp[type]++;
}
// 修改当前时间戳
int currentTime = lastTimestamp[type] - firstTimeStamp[type] + cnt[type];
raf.write(int2Bytes(currentTime), 1, 3);
Logger.print(" ,修改前timestamps 为:" + timestamp);
Logger.print(" ,当前timestamps 为:" + currentTime);
Logger.println();
}
}

/**
* 从尾部开始Check, 单纯检查,没有对文件进行操作
*
Expand All @@ -114,7 +158,9 @@ public static void checkFromEnd(String path) throws IOException {
int dataSize = 0;
try {
long position = raf.length();
while (true) {
//int remain = 20;
while (true) {//&& remain>=0
//remain--;
raf.seek(position - 4);
// 读取前一个tag size
int predataSize = readBytesToInt(raf, 4);
Expand Down Expand Up @@ -145,7 +191,8 @@ public static void checkFromEnd(String path) throws IOException {
firstTimeStamp = timestamp;
}
lastTimestamp = timestamp;
Logger.println(" ,当前timestamps 为:" + timestamp);
Logger.print(" ,当前timestamps 为:" + timestamp);
Logger.println();
raf.skipBytes(1);
// stream id 3个字节。总是0
raf.skipBytes(3);
Expand Down Expand Up @@ -187,91 +234,87 @@ private static int bytes2Int(byte[] bytes) {
int result = 0;
for (int i = 0; i < bytes.length; i++) {
result |= ((bytes[bytes.length - 1 - i] & 0xff) << (i * 8));
//System.out.printf("%x ",(bytes[i] & 0xff));
}
return result;
}

// private static byte[] double2Bytes(double d) {
// long value = Double.doubleToRawLongBits(d);
// byte[] byteRet = new byte[8];
// for (int i = 0; i < 8; i++) {
// byteRet[i] = (byte) ((value >> 8 * i) & 0xff);
// }
// byte[] byteReverse = new byte[8];
// for (int i = 0; i < 8; i++) {
// byteReverse[i] = byteRet[7 - i];
// // Logger.printf("%x ",byteReverse[i]);
// }
// // Logger.println();
// return byteReverse;
// }
private static byte[] double2Bytes(double d) {
long value = Double.doubleToRawLongBits(d);
byte[] byteRet = new byte[8];
for (int i = 0; i < 8; i++) {
byteRet[i] = (byte) ((value >> 8 * i) & 0xff);
}
byte[] byteReverse = new byte[8];
for (int i = 0; i < 8; i++) {
byteReverse[i] = byteRet[7 - i];
// Logger.printf("%x ",byteReverse[i]);
}
// Logger.println();
return byteReverse;
}

// private static double bytes2Double(byte[] arr) {
// byte[] byteReverse = new byte[8];
// for (int i = 0; i < 8; i++) {
// byteReverse[i] = arr[7 - i];
// }
//
// long value = 0;
// for (int i = 0; i < 8; i++) {
// value |= ((long) (byteReverse[i] & 0xff)) << (8 * i);
// }
// return Double.longBitsToDouble(value);
// }
public static double bytes2Double(byte[] arr) {
byte[] byteReverse = new byte[8];
for (int i = 0; i < 8; i++) {
byteReverse[i] = arr[7 - i];
}

long value = 0;
for (int i = 0; i < 8; i++) {
value |= ((long) (byteReverse[i] & 0xff)) << (8 * i);
}
return Double.longBitsToDouble(value);
}

// static byte[] buffer = new byte[1024 * 1024];
// static byte[] durationHeader = {0x08, 0x64 , 0x75 , 0x72 , 0x61 , 0x74 , 0x69 , 0x6f , 0x6e};
// static int pDurationHeader = 0;
// public static void changeDuration(String path, double duration) throws IOException{
// //08 64 75 72 61 74 69 6f 6e duration
// //00 bytes x8
// File file = new File(path);
// File fileNew = new File(path + ".flv");
// fileNew.delete();
// RandomAccessFile raf = new RandomAccessFile(file, "r");
// RandomAccessFile rafNew = new RandomAccessFile(fileNew, "rw");
//
// int lenRead = raf.read(buffer);
// int pDuration = checkBufferForDuration();
// boolean findDuration = false;
// long offset = 0;
// while (lenRead > -1) {
//
// if(pDuration == -1) {
// rafNew.write(buffer, 0, lenRead);
// }else { // 假设没有超过buffer界限
// findDuration = true;
// rafNew.write(buffer, 0, pDuration + 1);
// rafNew.write(0x00);
// rafNew.write(double2Bytes(duration));
// rafNew.write(buffer, pDuration + 10 , lenRead - pDuration - 10);
// }
// // Logger.println("当前完成度: " + cnt*100/total + "%");
// lenRead = raf.read(buffer);
// if(!findDuration) {
// pDuration = checkBufferForDuration();
// }
// offset += lenRead;
// }
// rafNew.close();
// raf.close();
//
// }
//
// /**
// * 检查buffer 是否包含duration头部
// * @return duration末尾在 buffer中的位置
// */
// static int checkBufferForDuration() {
// for(int i=0; i<buffer.length; i++) {
// if(buffer[i] == durationHeader[pDurationHeader]) {
// pDurationHeader ++;
// if(pDurationHeader == durationHeader.length) {
// pDurationHeader = 0;
// return i;
// }
// }
// }
// return -1;
// }
static byte[] buffer = new byte[1024 * 1024];
static byte[] durationHeader = {0x08, 0x64 , 0x75 , 0x72 , 0x61 , 0x74 , 0x69 , 0x6f , 0x6e};
static int pDurationHeader = 0;
public static void changeDuration(String path, double duration) throws IOException{
//08 64 75 72 61 74 69 6f 6e duration
//00 bytes x8
File file = new File(path);
File fileNew = new File(path + ".flv");
fileNew.delete();
RandomAccessFile raf = new RandomAccessFile(file, "rw");

int lenRead = raf.read(buffer);
int pDuration = checkBufferForDuration();
boolean findDuration = false;
while (lenRead > -1) {
long offset = 0;
if(pDuration != -1) {
findDuration = true;
raf.seek(offset + pDuration + 1);
raf.write(0x00);
raf.write(double2Bytes(duration));
break;
}
// Logger.println("当前完成度: " + cnt*100/total + "%");
lenRead = raf.read(buffer);
if(!findDuration) {
pDuration = checkBufferForDuration();
}
offset += lenRead;
}
raf.close();

}

/**
* 检查buffer 是否包含duration头部
* @return duration末尾在 buffer中的位置
*/
static int checkBufferForDuration() {
for(int i=0; i<buffer.length; i++) {
if(buffer[i] == durationHeader[pDurationHeader]) {
pDurationHeader ++;
if(pDurationHeader == durationHeader.length) {
pDurationHeader = 0;
return i;
}
}
}
return -1;
}
}
Loading

0 comments on commit b8eb544

Please sign in to comment.