Java代码实现媒体格式转换:M3U8(ts文件)转MP4

功能简介

以代码实现媒体格式转换原理:.m3u8 转 .mp4

参考资料


Java

环境

JDK-8
无第三方 jar 依赖

Git源码

核心代码逻辑

Core.java

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Core {
	/**
	 * 解密文件
	 * @param f {@link File}:ts文件;
	 * @param k {@link byte}[]:密钥。由#EXT-X-KEY标签的URI属性获取;
	 * @param iv {@link String}:AES初始化向量。#EXT-X-KEY标签的IV属性;
	 * @return {@link InputStream}:解密流。
	 * @author Haining.Liu
	 * @date 2024-8-8
	 */
	public static File decryptFile(File f, String method, byte[] k, String iv) {
		byte[] tbs = read(f);
		if (tbs == null || tbs.length == 0) {
			return null;
		}
		if ("AES-128".equals(method)) {
			tbs = AES.decryptCBC(tbs, k, doIV(iv));
		}
		return outFile(f.getPath() + ".mp4", new ByteArrayInputStream(tbs));
	}

	/**
	 * 读取整文件
	 * @param f {@link File}:文件;
	 * @date 2024-8-8
	 */
	private static byte[] read(File f) {
		try (FileInputStream in = new FileInputStream(f);) {
			byte[] bs = new byte[in.available()];
			in.read(bs);
			return bs;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return new byte[0];
	}
	/**
	 * 输出文件
	 * @param fileName {@link String}:输出文件名;
	 * @param in {@link InputStream}:解析完的文件流;
	 * @return {@link File}:生成的文件。
	 * @date 2024-8-8
	 */
	private static File outFile(String fileName, InputStream in) {
		File f = new File(fileName);
		try (
				FileOutputStream out = new FileOutputStream(f);
				) {
			byte[] bs = new byte[1024 << 7];
			for (int rl; (rl = in.read(bs)) != -1;) {
				out.write(bs, 0, rl);
			}
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return f;
	}
	/**
	 * IV转byte字节
	 */
	private static byte[] doIV(String iv) {
		if (iv.startsWith("0x")) {
			iv = iv.substring(2);
			return hexToBytes(iv);
		} else {
			return iv.getBytes(StandardCharsets.UTF_8);
		}
	}
	/**
	 * 16进制转byte
	 */
	public static byte[] hexToBytes(String s) {
		int radix = 16;
		BigInteger hex = new BigInteger(s, radix);
        ByteBuffer bf = ByteBuffer.allocate(radix);
        bf.put(hex.toByteArray());
        return bf.array();
	}

	/**
	 * AES加解密
	 * @date 2024-8-8
	 */
	public static class AES {
		private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
		static {
			// java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());	// 依赖第三方包注册"AES/CBC/PKCS7Padding"
		}
	
		/**
		 * AES-CBC模式解密
		 * @param bs byte[]:被解密内容;
		 * @param k byte[]:密钥;
		 * @param iv {@link String}:初始化向量(‌IV)‌(‌16字节)‌;
		 * @return byte[]:解密后的内容。
		 * @date 2024-8-8
		 */
		public static byte[] decryptCBC(byte[] bs, byte[] k, byte[] iv) {
			Optional<IvParameterSpec> opt = Optional.ofNullable(iv)
					.map(AES::fillIV)
					.map(IvParameterSpec::new);
			try {
				SecretKeySpec sks = new SecretKeySpec(fill(k), "AES");
				Cipher ci = Cipher.getInstance(ALGORITHM);
				if (opt.isPresent()) {
					ci.init(Cipher.DECRYPT_MODE, sks, opt.get());
				} else {
					ci.init(Cipher.DECRYPT_MODE, sks);
				}
				return ci.doFinal(bs);
			} catch (NoSuchAlgorithmException e) {
				System.out.println("无此算法");
			} catch (NoSuchPaddingException e) {
				System.out.println("无此填充");
			} catch (InvalidKeyException e) {
				System.out.println("无此密钥");
			} catch (InvalidAlgorithmParameterException e) {
				System.out.println("无此算法参数");
			} catch (IllegalBlockSizeException e) {
				System.out.println("非法块大小");
			} catch (BadPaddingException e) {
				System.out.println("不良填充");
			}
			return bs;
		}
		/**
		 * 填充初始化向量(‌IV)
		 */
		private static byte[] fillIV(byte[] k) {
			return fill(k, (byte) 16);
		}
		/**
		 * 填充
		 */
		private static byte[] fill(byte[] k) {
			return fill(k, (byte) 0);
		}
		/**
		 * 填充
		 */
		private static byte[] fill(byte[] k, byte len) {
			int kl = k.length;
			if (len > 0) {
				if (kl == len) {
					return k;
				}
			} else if (kl == 16
					|| kl == 24
					|| kl == 32) {
				return k;
			} else if (kl < 16) {
				len = 16;
			} else if (kl < 24) {
				len = 24;
			} else {
				len = 32;
			}
			byte[] bs = new byte[ len ];
			// Arrays.fill(bs, (byte) '0');	// 右侧补'0'
			System.arraycopy(k, 0, bs, 0, kl < len ? kl : len);
			return bs;
		}
	}
}

测试示例

	public static void main(String[] args) {
		File f = new File("D:\\m3u8\\0.ts");	// ts媒体文件
		File kf = new File("D:\\m3u8\\K.key");	// 密钥文件,由#EXT-X-KEY标签的URI属性获取
		byte[] k = read(kf);
		String method = /* null */ "AES-128";
		File outFile = decryptFile(f, method, k, "0x0123456789abcdef0123456789abcdef");
		System.out.println("解密的文件:" + outFile);
	}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值