Java實(shí)現(xiàn)提取QSV文件視頻內(nèi)容
創(chuàng)建類(lèi)
第一步新建一個(gè)java類(lèi)QSV
,構(gòu)造函數(shù)傳入需要解析的文件名稱(chēng)。
public class QSV { private RandomAccessFile randomAccessFile; private String name; public QSV(String name) throws FileNotFoundException { randomAccessFile = new RandomAccessFile(name, "r"); this.name = name; } }
通用方法
逐字節(jié)讀取文件
我們需要逐個(gè)字節(jié)讀取文件,這邊就需要jdk自帶的類(lèi)RandomAccessFile
,先定義一個(gè)通用函數(shù),輸出偏移位置和字節(jié)大小獲取字節(jié)數(shù)組。
/** * @param offset 偏移未知 * @param size 字節(jié)大小 * @return 字節(jié)數(shù)組 * @throws IOException */ private byte[] getByteFromFile(int offset, int size) throws IOException { //指針移動(dòng)至偏移位置 randomAccessFile.seek(offset); //需要讀取的字節(jié)數(shù) byte[] bs = new byte[size]; randomAccessFile.read(bs); return bs; }
字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制字符串
private static final char[] HEX_VOCABLE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; /** * 字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制字符串 * * @param bs * @return */ public String bytesToHex(byte[] bs) { StringBuilder sb = new StringBuilder(); for (byte b : bs) { int high = (b >> 4) & 0x0f; int low = b & 0x0f; sb.append(HEX_VOCABLE[high]); sb.append(HEX_VOCABLE[low]); } return sb.toString(); }
字節(jié)數(shù)組轉(zhuǎn)int
/** * 字節(jié)數(shù)組轉(zhuǎn)int * * @param b * @return */ public int toInt(byte[] b) { int res = 0; for (int i = 0; i < b.length; i++) { res += (b[i] & 0xff) << (i * 8); } return res; }
老版本解密算法
老版本解密方法比較簡(jiǎn)單,先看下C的寫(xiě)法:
// 定義1個(gè)字節(jié)內(nèi)存 typedef uint8_t BYTE; // 定義4個(gè)字節(jié)內(nèi)存 typedef uint32_t DWORD; // decryption algorithm for some segments in qsv version 0x1 void decrypt_1(BYTE* buffer, DWORD size) { static BYTE dict[] = {0x62, 0x67, 0x70, 0x79}; for(int i = 0; i < size; ++i) { DWORD j = ~i & 0x3; buffer[i] ^= dict[j]; } }
可以直接用byte數(shù)組和long類(lèi)型替換c的內(nèi)存定義,這邊沒(méi)問(wèn)題時(shí)因?yàn)闆](méi)有位移算法,后面新版本解密算法會(huì)講到。
private void decrypt_1(byte[] bs, long len) { byte[] b = new byte[]{0x62, 0x67, 0x70, 0x79}; for (int i = 0; i < len; i++) { //先取反再與運(yùn)算 int j = ~i & 0x3; //異或運(yùn)算 bs[i] ^= b[j]; } }
新版本解密算法
新版本解密算法比較復(fù)雜,同樣先看下c的寫(xiě)法:
// 定義1個(gè)字節(jié)內(nèi)存 typedef uint8_t BYTE; // 定義4個(gè)字節(jié)內(nèi)存 typedef uint32_t DWORD; // decryption algorithm for some segments in qsv version 0x2 void decrypt_2(BYTE* buffer, DWORD size) { DWORD x = 0x62677079; for(DWORD i = size - 1; i != 0; --i) { x = (x << 1) | (x >> 31); x ^= buffer[i]; } for(DWORD i = 1; i < size; ++i) { x ^= buffer[i] & 0xFF; x = (x >> 1) | (x << 31); DWORD j = x % i; BYTE tmp = buffer[j]; buffer[j] = tmp ^ (BYTE)~buffer[i]; buffer[i] = tmp; } }
這邊注意c的寫(xiě)法有位移,定義的DWORD
占32位內(nèi)存,而java與之對(duì)應(yīng)的int
類(lèi)型也是占32位內(nèi)存,取值范圍是-2^31~2^31-1
(即-2147483648~2147483647
),如果超出這個(gè)范圍java需要用long,而long占64位內(nèi)存取反和位移都是按64位進(jìn)行的與C的32位取反位移計(jì)算結(jié)果肯定有差異。
比如:2147483648,轉(zhuǎn)換位二進(jìn)制表示:10000000000000000000000000000000,C的寫(xiě)法由于DWORD
占32位內(nèi)存,左移一位后超出32為故就變成00000000000000000000000000000000,而java已經(jīng)超出了int的取值范圍,只能用long定義,由于long占64位,左移一位不會(huì)超出,結(jié)果也就不一樣了。
又比如:9147483648,轉(zhuǎn)為二進(jìn)制表示:1000100001001110111000011000000000,已經(jīng)超出32位,存到DWORD
中的只保留右邊的32位,即00100001001110111000011000000000,左移以為變成01000010011101110000110000000000,轉(zhuǎn)為10進(jìn)制為1115098112,而java采用long定義不會(huì)超出64位,左移結(jié)果位10001000010011101110000110000000000,轉(zhuǎn)為十進(jìn)制表示為18294967296,與C的結(jié)果相差較大。那么有辦法解決嗎,答案肯定是有的,我們需要重新寫(xiě)一個(gè)位移算法。
/** * 左移 * * @param value long * @param i 位移量 * @return long */ private long longLeftShift(long value, int i) { String binary = format32(value); binary = binary.substring(i) + String.format("%0" + i + "d", 0); return Long.parseLong(binary, 2); } /** * 右移 * * @param value long * @param i 位移量 * @return long */ private long longRightShift(long value, int i) { String binary = format32(value); binary = String.format("%0" + i + "d", 0) + binary.substring(0, binary.length() - i); return Long.parseLong(binary, 2); } private String format32(long value) { String binary = Long.toBinaryString(value); if (binary.length() < 32) { //補(bǔ)滿(mǎn)32位 binary = String.format("%0" + (32 - binary.length()) + "d", 0) + binary; } else { //多余的截取掉 binary = binary.substring(binary.length() - 32); } return binary; }
有了新定義的位移算法,解密算法就可以修改為:
private void decrypt_2(byte[] buffer, int size) { long x = 0x62677079; for (int i = size - 1; i != 0; --i) { x = longLeftShift(x, 1) | longRightShift(x, 31); x ^= buffer[i] & 0xFF; } for (int i = 1; i < size; ++i) { x ^= buffer[i] & 0xFF; x = longRightShift(x, 1) | longLeftShift(x, 31); int j = (int) (x % i); byte tmp = buffer[j]; int a = buffer[i]; buffer[j] = (byte) (tmp ^ ~a); buffer[i] = tmp; } }
獲取QSV頭部信息
上一篇文章講到,頭部信息共90個(gè)字節(jié),signature
(10byte)+version
(4byte)+vid
(16byte)+_unknown1
(4byte)+_unknown2
(32byte)+_unknown3
(4byte)+_unknown4
(4byte)+json_offset
(8byte)+json_size
(4byte)+nb_indices
(4byte)
public static void main(String[] args) throws IOException { QSV qsv = new QSV("D:\\workspace\\c\\風(fēng)吹半夏第1集-藍(lán)光1080P.qsv"); qsv.readHead(); qsv.close(); } public void readHead() throws IOException { //signature:10byte byte[] signature = getByteFromFile(0x0, 0xA); //version:4byte byte[] version = getByteFromFile(0xA, 0x4); //vid:16byte byte[] vid = getByteFromFile(0xE, 0x10); //_unknown1:4byte byte[] _unknown1 = getByteFromFile(0x1E, 0x4); //_unknown2:32byte byte[] _unknown2 = getByteFromFile(0x22, 0x20); //_unknown3:4byte byte[] _unknown3 = getByteFromFile(0x42, 0x4); //_unknown4:4byte byte[] _unknown4 = getByteFromFile(0x46, 0x4); //json_offset:8byte byte[] json_offset = getByteFromFile(0x4A, 0x8); //json_size:4byte byte[] json_size = getByteFromFile(0x52, 0x4); //nb_indices:4byte byte[] nb_indices = getByteFromFile(0x56, 0x4); System.out.println(String.format("signature:%s\nversion:%s\nvid:%s\n_unknown1:%s\n_unknown2:%s\n_unknown3:%s\n_unknown4:%s\njson_offset:0x%s\njson_size:0x%s\nnb_indices:0x%s", new String(signature), toInt(version), bytesToHex(vid), bytesToHex(_unknown1), bytesToHex(_unknown2), bytesToHex(_unknown3), bytesToHex(_unknown4), Integer.toHexString(toInt(json_offset)), Integer.toHexString(toInt(json_size)), Integer.toHexString(toInt(nb_indices)))); }
運(yùn)行結(jié)果:
signature:QIYI VIDEO
version:2
vid:A90EA47D4333331BB85F331C1130504C
_unknown1:01000000
_unknown2:0000000000000000000000000000000000000000000000000000000000000000
_unknown3:01000000
_unknown4:01000000
json_offset:0x167
json_size:0x1d81
nb_indices:0x7
獲取json數(shù)據(jù)
獲取json數(shù)據(jù)需要先獲取頭部信息記錄的json的偏移位置和字節(jié)大小,獲取到的字節(jié)數(shù)組再通過(guò)老版本解密算法解密即可。
public static void main(String[] args) throws IOException { QSV qsv = new QSV("D:\\workspace\\c\\風(fēng)吹半夏第1集-藍(lán)光1080P.qsv"); qsv.readJson(); qsv.close(); } public void readJson() throws IOException { //json_offset:8byte byte[] json_offset = getByteFromFile(0x4A, 0x8); //json_size:4byte byte[] json_size = getByteFromFile(0x52, 0x4); //json byte[] bs = getByteFromFile(toInt(json_offset), toInt(json_size)); decrypt_1(bs, bs.length); System.out.println(String.format("json_offset:0x%s\njson_size:0x%s", Integer.toHexString(toInt(json_offset)), Integer.toHexString(toInt(json_size)))); System.out.println(new String(bs, StandardCharsets.UTF_8)); }
運(yùn)行結(jié)果:
json_offset:0x167
json_size:0x1d81
QYVI {"qsv_info":{"ad":{},"aid":"3644740799867701","bid":600,"dr":-1,"independentaudio":true,"m3u8":"","pano":{"type":1},"qsvinfo_version":2,"sdv":"","st":"","thdt":1,"tht":0,"title":"","title_tail_info":{"bt":99,"bts":-1,"et":2621,"ete":-1,"fe":1,"le":0},"tvid":"3185901936393500","vd":{"seg":{"duration":["376140","365508","370300","371477","368311","368308","554319"],"rid":["a8c47c2906b88ae47e8503c96dae0494","4c8578cecbdfa3137a61b7e2bd1c68dc","4bc76f9b4d4ce995f40b42c467d206f8","8ed13ccd56aa2367f5eaabee12043d89","247b4612415c5b452f5965894a967e19","309400bef063cfd6baf19feb0d4a73a9","9771b1042cabfc9828767a3d0350f655"],"size":["75624801","84329329","90135782","82093900","91974933","75298884","111399115"]},"time":{"et":"2621000","st":"99000"}},"vi":"{\"writer\":\"\",\"authors\":\"\",\"upOrder\":36,\"rewardAllowed\":0,\"fl\":[],\"allowEditVVIqiyi\":0,\"isPopup\":1,\"payMark\":0,\"an\":\"風(fēng)吹半夏\",\"pvu\":\"\",\"subType\":1,\"rewardMessage\":\"\",\"ipLimit\":1,\"pubTime\":\"1673306651000\",\"mainActorRoles\":[],\"up\":\"2023-01-17 20:20:02\",\"un\":\"\",\"qiyiProduced\":1,\"platforms\":[],\"vn\":\"風(fēng)吹半夏第1集\",\"plc\":{\"4\":{\"downAld\":1,\"coa\":1},\"40\":{\"downAld\":1,\"coa\":1},\"10\":{\"downAld\":1,\"coa\":1},\"5\":{\"downAld\":1,\"coa\":1},\"41\":{\"downAld\":1,\"coa\":1},\"22\":{\"downAld\":1,\"coa\":1},\"32\":{\"downAld\":1,\"coa\":1},\"11\":{\"downAld\":1,\"coa\":1},\"12\":{\"downAld\":1,\"coa\":1},\"7\":{\"downAld\":1,\"coa\":1},\"13\":{\"downAld\":1,\"coa\":1},\"14\":{\"downAld\":1,\"coa\":1},\"91\":{\"downAld\":1,\"coa\":1},\"34\":{\"downAld\":1,\"coa\":1},\"9\":{\"downAld\":1,\"coa\":1},\"6\":{\"downAld\":1,\"coa\":1},\"16\":{\"downAld\":1,\"coa\":1},\"17\":{\"downAld\":1,\"coa\":1},\"27\":{\"downAld\":1,\"coa\":1},\"28\":{\"downAld\":1,\"coa\":1},\"18\":{\"downAld\":1,\"coa\":1},\"29\":{\"downAld\":1,\"coa\":1},\"1\":{\"downAld\":1,\"coa\":1},\"50\":{\"downAld\":1,\"coa\":1},\"31\":{\"downAld\":1,\"coa\":1},\"15\":{\"downAld\":1,\"coa\":1},\"2\":{\"downAld\":1,\"coa\":1},\"3\":{\"downAld\":1,\"coa\":1},\"21\":{\"downAld\":1,\"coa\":1},\"30\":{\"downAld\":1,\"coa\":1},\"19\":{\"downAld\":1,\"coa\":1},\"8\":{\"downAld\":1,\"coa\":1},\"20\":{\"downAld\":1,\"coa\":1}},\"sm\":0,\"st\":200,\"startTime\":99,\"showChannelId\":2,\"vu\":\"http:\\/\\/www.iqiyi.com\\/v_uvxuws1kkw.html\",\"povu\":\"\",\"ppsuploadid\":0,\"qiyiPlayStrategy\":\"非會(huì)員每日20:00轉(zhuǎn)免1集\",\"ar\":\"內(nèi)地\",\"cc\":0,\"videoQipuId\":3185901936393500,\"albumQipuId\":3644740799867701,\"pano\":{\"type\":1},\"subt\":\"許半夏幫童驍騎驅(qū)霉運(yùn)\",\"endTime\":2621,\"cpa\":1,\"userVideocount\":0,\"es\":36,\"plg\":2774,\"stl\":{\"d\":\"http:\\/\\/meta.video.iqiyi.com\",\"stl\":[]},\"subKey\":\"3644740799867701\",\"tvFocuse\":\"趙麗穎歐豪攜手創(chuàng)業(yè)\",\"qtId\":0,\"ty\":0,\"ppsInfo\":{\"shortTitle\":\"風(fēng)吹半夏第1集\",\"name\":\"風(fēng)吹半夏第1集\"},\"sid\":0,\"etm\":\"\",\"keyword\":\"風(fēng)吹半夏\",\"pturl\":\"\",\"a\":\"\",\"isTopChart\":0,\"idl\":0,\"albumAlias\":\"許半夏幫童驍騎驅(qū)霉運(yùn)\",\"stm\":\"\",\"pd\":1,\"tags\":[],\"coa\":0,\"c\":2,\"producers\":\"\",\"nurl\":\"\",\"tvEname\":\"wild bloom\",\"uid\":\"0\",\"asubt\":\"\",\"d\":\"傅東育|毛溦\(chéng)",\"vid\":\"a90ea47d4333331bb85f331c1130504c\",\"dts\":\"20230117202002\",\"editorInfo\":\"\",\"tvSeason\":0,\"ntvd\":3185901936393500,\"vpic\":\"http:\\/\\/pic6.iqiyipic.com\\/image\\/20221127\\/e2\\/52\\/v_170312139_m_601_m1.jpg\",\"vType\":0,\"cType\":1,\"payMarkUrl\":\"\",\"apic\":\"http:\\/\\/pic8.iqiyipic.com\\/image\\/20230104\\/6c\\/00\\/a_100498846_m_601_m26.jpg\",\"shortTitle\":\"風(fēng)吹半夏第1集\",\"tvid\":3185901936393500,\"aid\":3644740799867701,\"au\":\"http:\\/\\/www.iqiyi.com\\/a_yq6d69gtw9.html\",\"tvPhase\":0,\"ifs\":0,\"is\":0,\"tpl\":[],\"actors\":[],\"isVip\":\"\",\"votes\":[],\"previewImageUrl\":\"http:\\/\\/preimage3.iqiyipic.com\\/preimage\\/20230110\\/6a\\/a6\\/v_170312139_m_611_m6.jpg\",\"e\":\"\",\"cnPremTime\":0,\"s\":\"\",\"ma\":\"趙麗穎|歐豪|李光潔|劉威|柯藍(lán)|任重|馮嘉怡|孫千|黃澄澄|是安|宋熹|王西|黃義威|王勁松|劉威葳|尤靖茹|林鵬(大陸男演員)|郝平|淮文|顏世魁|薛淑杰|劉碩|丁冠中|豆藝?yán)寇鐘吁|楊德民|傅宜箴|朱超藝|黃子琪|榮飛|田玲|周小鑌|孔琳|方曉莉|陳創(chuàng)|韓朔|高遠(yuǎn)\",\"supName\":\"\",\"sc\":0,\"categoryKeywords\":\"電視劇,3644740799867701,0,http:\\/\\/list.iqiyi.com\\/www\\/2\\/------------------.html 自制,12791537149997,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-12791537149997-----------------.html 浙江衛(wèi)視,20291787825994,0,http:\\/\\/list.iqiyi.com\\/www\\/2\\/------------------.html 家庭,23757144288056,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-23757144288056-----------------.html 言情,23789230921965,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-23789230921965-----------------.html 勵(lì)志,29717236536467,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-29717236536467-----------------.html 商戰(zhàn),32284167249109,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-32284167249109-----------------.html 劇情,37979544767779,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-37979544767779-----------------.html 都市,80141381722890,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-80141381722890-----------------.html 內(nèi)地,80526421329786,0,http:\\/\\/list.iqiyi.com\\/www\\/2\\/------------------.html 愛(ài)情,87328787718281,2,http:\\/\\/list.iqiyi.com\\/www\\/2\\/-87328787718281-----------------.html\",\"onlineStatus\":1,\"mdown\":0,\"bmsg\":{\"t\":\"20230117202002\",\"f\":\"web\",\"mid\":\"\",\"sp\":\"26173601,\"},\"nvid\":\"8ec7f862076f40ee17c908e2bbc3fae2\",\"flpps\":[],\"supType\":\"\",\"lg\":2,\"is3D\":0,\"bossStatus\":0,\"isDisplayCircle\":0,\"actorRoles\":[],\"exclusive\":1,\"circle\":{\"type\":0,\"id\":0},\"commentAllowed\":1,\"tg\":\"忠犬 大女主 自制 工業(yè)強(qiáng)國(guó) 毒舌 重組家庭 渣男 浙江衛(wèi)視 家庭 言情 霸道總裁 御姐 勵(lì)志 個(gè)人成長(zhǎng) 虐戀 雙強(qiáng) 普通話 商戰(zhàn) 硬漢 劇情 出軌 兄弟反目 女強(qiáng)人 爽劇 初戀 青年奮斗 熱血 女性勵(lì)志 群像 個(gè)人奮斗 接地氣 女青年 大佬 原生家庭 豬隊(duì)友 都市 內(nèi)地 組團(tuán) 愛(ài)情\",\"info\":\"1996年的秋天,陽(yáng)光炫目。許半夏開(kāi)著一輛桑塔納,接著剛出獄的童驍騎駛往鷺州出差。一路上童驍騎默默無(wú)言,二人之間氣氛尷尬。到了酒店后,許半夏的指示很清楚:洗澡,看電視,睡覺(jué),第二天一早,她來(lái)接童驍騎一起出差。童驍騎點(diǎn)點(diǎn)頭,在許半夏離開(kāi)后,他開(kāi)始洗澡,記憶也回到了五年前……\\n1991年的一個(gè)雨夜,童驍騎因?yàn)榧敝o媽媽籌醫(yī)藥費(fèi),撬了一堆下水井蓋,蹬著三輪車(chē)?yán)搅嗽S半夏和小陳的廢品站。小陳認(rèn)定童驍騎就是個(gè)騙子,不愿幫忙,但善良的半夏相信童驍騎,給了他錢(qián),并且三個(gè)人一大早一起把井蓋都送了回去。童驍騎為表感激,愿意替二人做事。許半夏提到當(dāng)?shù)劁搹S有很多廢了的下腳料,他們可以去收,但缺一輛大車(chē)。于是司機(jī)班出身的童驍騎真的把廠里的東風(fēng)偷偷開(kāi)了出來(lái),三人搖搖晃晃進(jìn)了鋼廠,掙到了人生的第一桶金,但童驍騎也因?yàn)樗阶杂密?chē)被廠里開(kāi)除了?;氐綇U品站的童驍騎心灰意冷,許半夏卻安慰道正好,今后他們?nèi)齻€(gè)人就一塊兒干,而這些錢(qián)就是他們的第一筆合伙資金。\",\"supId\":0,\"issueTime\":0,\"dtype\":3,\"producer\":\"\",\"presentor\":[],\"followerCount\":0}","vid":"a90ea47d4333331bb85f331c1130504c","videotype":0}}s
提取視頻信息
獲取視頻信息,首先要先提取索引信息,根據(jù)索引信息中的偏移位置和字節(jié)大小獲取,獲取到的字節(jié)數(shù)組前1024個(gè)字節(jié)需要解密,根據(jù)頭部信息中的version
判斷,如果是1則用老版本算法解密,如果是2則用新版本算法解密。
一個(gè)QSV文件包含多段視頻數(shù)據(jù),已發(fā)現(xiàn)的視頻格式有flv(舊版客戶(hù)端)、mpeg-ts(新版客戶(hù)端)。可以通過(guò)前三個(gè)字節(jié)判斷,如果前三個(gè)字節(jié)轉(zhuǎn)字符串為FLV
則為FLV
格式。
public static void main(String[] args) throws IOException { QSV qsv = new QSV("D:\\workspace\\c\\風(fēng)吹半夏第1集-藍(lán)光1080P.qsv"); qsv.readIndex("D:\\workspace\\c"); qsv.close(); } public void readIndex(String outPath) throws IOException { //nb_indices byte[] nb_indices = getByteFromFile(0x56, 0x4); //索引 //_unknown_flag byte[] _unknown_flag = getByteFromFile(0x5A, toInt(nb_indices) + 7 >> 3); System.out.println(String.format("nb_indices:0x%s\n_unknown_flag:%s", Integer.toHexString(toInt(nb_indices)), bytesToHex(_unknown_flag))); //version:4byte byte[] version = getByteFromFile(0xA, 0x4); //索引體 byte[] indices = getByteFromFile(0x5A + (toInt(nb_indices) + 7 >> 3), toInt(nb_indices) * 0x1C); for (int i = 0; i < toInt(nb_indices); i++) { byte[] indice = new byte[0x1C]; System.arraycopy(indices, 0x1C * i, indice, 0, 0x1C); if (toInt(version) == 1) { decrypt_1(indice, indice.length); } else if (toInt(version) == 2) { decrypt_2(indice, indice.length); } //索引體 //_codetable byte[] _codetable = new byte[0x10]; System.arraycopy(indice, 0, _codetable, 0, 0x10); //segment_offset byte[] segment_offset = new byte[0x8]; System.arraycopy(indice, 0x10, segment_offset, 0, 0x8); //segment_size byte[] segment_size = new byte[0x4]; System.arraycopy(indice, 0x18, segment_size, 0, 0x4); System.out.println(String.format("_codetable:%s,segment_offset:0x%s,segment_size:0x%s", bytesToHex(_codetable), Integer.toHexString(toInt(segment_offset)), Integer.toHexString(toInt(segment_size)))); exportVideo(i + 1, segment_offset, segment_size, outPath); } } private void exportVideo(int fileindex, byte[] segment_offset, byte[] segment_size, String outPath) throws IOException { byte[] file = getByteFromFile(toInt(segment_offset), toInt(segment_size)); //前1024字節(jié)加密 decrypt_2(file, 1024); byte[] head = new byte[0x3]; System.arraycopy(file, 0, head, 0, 0x3); //System.out.println(new String(head)); String outname = name.substring(name.lastIndexOf(File.separator) + 1); String outFile = outPath + File.separator + outname.substring(0, outname.lastIndexOf(".")) + "-第" + fileindex + "段." + new String(head).toLowerCase(); RandomAccessFile write = new RandomAccessFile(outFile, "rw"); System.out.println("export:" + outFile); write.write(file); write.close(); }
運(yùn)行結(jié)果:
nb_indices:0x7
_unknown_flag:7F
_codetable:A8C47C2906B88AE47E8503C96DAE0494,segment_offset:0x1ee8,segment_size:0x481f161
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第1段.flv
_codetable:4C8578CECBDFA3137A61B7E2BD1C68DC,segment_offset:0x4821049,segment_size:0x506c371
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第2段.flv
_codetable:4BC76F9B4D4CE995F40B42C467D206F8,segment_offset:0x988d3ba,segment_size:0x55f5ce6
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第3段.flv
_codetable:8ED13CCD56AA2367F5EAABEE12043D89,segment_offset:0xee830a0,segment_size:0x4e4a74c
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第4段.flv
_codetable:247B4612415C5B452F5965894A967E19,segment_offset:0x13ccd7ec,segment_size:0x57b6d15
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第5段.flv
_codetable:309400BEF063CFD6BAF19FEB0D4A73A9,segment_offset:0x19484501,segment_size:0x47cf844
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第6段.flv
_codetable:9771B1042CABFC9828767A3D0350F655,segment_offset:0x1dc53d45,segment_size:0x6a3d0cb
export:D:\workspace\c\風(fēng)吹半夏第1集-藍(lán)光1080P-第7段.flv
參考資料
本文參考開(kāi)源項(xiàng)目qsv2flv
,項(xiàng)目地址:https://github.com/btnkij/qsv2flv.git
該項(xiàng)目采用c
語(yǔ)言和Qt
開(kāi)發(fā),本文作者根據(jù)原理采用Java
進(jìn)行了翻寫(xiě),僅供學(xué)習(xí)和參考。如果要用于實(shí)際應(yīng)用,請(qǐng)使用原項(xiàng)目提供的工具,可以將qsv
文件轉(zhuǎn)換為flv
或mp4
格式。
到此這篇關(guān)于Java實(shí)現(xiàn)提取QSV文件視頻內(nèi)容的文章就介紹到這了,更多相關(guān)Java提取QSV文件視頻內(nèi)容內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文搞懂spring boot本地事務(wù)@Transactional參數(shù)
這篇文章主要介紹了spring boot本地事務(wù)@Transactional參數(shù)詳解,本文通過(guò)示例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10Spring中@ConditionalOnProperty注解的作用詳解
這篇文章主要介紹了Spring中@ConditionalOnProperty注解的作用詳解,@ConditionalOnProperty注解主要是用來(lái)判斷配置文件中的內(nèi)容來(lái)決定配置類(lèi)是否生效用的,如果條件不匹配,則配置類(lèi)不生效,需要的朋友可以參考下2024-01-01Java開(kāi)發(fā)者必備10大數(shù)據(jù)工具和框架
這篇文章主要為大家詳細(xì)介紹了Java開(kāi)發(fā)者必備10大數(shù)據(jù)工具和框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Spring Core動(dòng)態(tài)代理的實(shí)現(xiàn)代碼
通過(guò)JDK的Proxy方式或者CGLIB方式生成代理對(duì)象的時(shí)候,相關(guān)的攔截器已經(jīng)配置到代理對(duì)象中去了,接下來(lái)通過(guò)本文給大家介紹Spring Core動(dòng)態(tài)代理的相關(guān)知識(shí),需要的朋友可以參考下2021-10-10IDEA無(wú)法打開(kāi)Marketplace的三種解決方案(推薦)
這篇文章主要介紹了IDEA無(wú)法打開(kāi)Marketplace的三種解決方案(推薦),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11Java實(shí)現(xiàn)統(tǒng)計(jì)在線人數(shù)功能的方法詳解
很多人在筆試或者面試中問(wèn)到:現(xiàn)在要你實(shí)現(xiàn)一個(gè)統(tǒng)計(jì)在線人數(shù)的功能,你該怎么設(shè)計(jì)?不知道的朋友,這篇文章就來(lái)告訴你具體實(shí)現(xiàn)方法2022-08-08