blob: 74246d06d3d1f4b53737dbcd694f604b5a63f725 [file] [log] [blame]
package com.googlecode.mp4parser.boxes.piff;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.IsoTypeWriter;
import com.googlecode.mp4parser.util.Path;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.7. ASF GUIDs
* <p/>
* <p/>
* ASF_Protection_System_Identifier_Object
* 9A04F079-9840-4286-AB92E65BE0885F95
* <p/>
* ASF_Content_Protection_System_Microsoft_PlayReady
* F4637010-03C3-42CD-B932B48ADF3A6A54
* <p/>
* ASF_StreamType_PlayReady_Encrypted_Command_Media
* 8683973A-6639-463A-ABD764F1CE3EEAE0
* <p/>
* <p/>
* Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.5. Data Objects > 2.5.1. Payload Extension for AES in Counter Mode
* <p/>
* The sample Id is used as the IV in CTR mode. Block offset, starting at 0 and incremented by 1 after every 16 bytes, from the beginning of the sample is used as the Counter.
* <p/>
* The sample ID for each sample (media object) is stored as an ASF payload extension system with the ID of ASF_Payload_Extension_Encryption_SampleID = {6698B84E-0AFA-4330-AEB2-1C0A98D7A44D}. The payload extension can be stored as a fixed size extension of 8 bytes.
* <p/>
* The sample ID is always stored in big-endian byte order.
*/
public class PlayReadyHeader extends ProtectionSpecificHeader {
private long length;
private List<PlayReadyRecord> records;
public PlayReadyHeader() {
}
@Override
public void parse(ByteBuffer byteBuffer) {
/*
Length DWORD 32
PlayReady Record Count WORD 16
PlayReady Records See Text Varies
*/
length = IsoTypeReader.readUInt32BE(byteBuffer);
int recordCount = IsoTypeReader.readUInt16BE(byteBuffer);
records = PlayReadyRecord.createFor(byteBuffer, recordCount);
}
@Override
public ByteBuffer getData() {
int size = 4 + 2;
for (PlayReadyRecord record : records) {
size += 2 + 2;
size += record.getValue().rewind().limit();
}
ByteBuffer byteBuffer = ByteBuffer.allocate(size);
IsoTypeWriter.writeUInt32BE(byteBuffer, size);
IsoTypeWriter.writeUInt16BE(byteBuffer, records.size());
for (PlayReadyRecord record : records) {
IsoTypeWriter.writeUInt16BE(byteBuffer, record.type);
IsoTypeWriter.writeUInt16BE(byteBuffer, record.getValue().limit());
ByteBuffer tmp4debug = record.getValue();
byteBuffer.put(tmp4debug);
}
return byteBuffer;
}
public void setRecords(List<PlayReadyRecord> records) {
this.records = records;
}
public List<PlayReadyRecord> getRecords() {
return Collections.unmodifiableList(records);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("PlayReadyHeader");
sb.append("{length=").append(length);
sb.append(", recordCount=").append(records.size());
sb.append(", records=").append(records);
sb.append('}');
return sb.toString();
}
public static abstract class PlayReadyRecord {
int type;
public PlayReadyRecord(int type) {
this.type = type;
}
public static List<PlayReadyRecord> createFor(ByteBuffer byteBuffer, int recordCount) {
List<PlayReadyRecord> records = new ArrayList<PlayReadyRecord>(recordCount);
for (int i = 0; i < recordCount; i++) {
PlayReadyRecord record;
int type = IsoTypeReader.readUInt16BE(byteBuffer);
int length = IsoTypeReader.readUInt16BE(byteBuffer);
switch (type) {
case 0x1:
record = new RMHeader();
break;
case 0x2:
record = new DefaulPlayReadyRecord(0x02);
break;
case 0x3:
record = new EmeddedLicenseStore();
break;
default:
record = new DefaulPlayReadyRecord(type);
}
record.parse((ByteBuffer) byteBuffer.slice().limit(length));
byteBuffer.position(byteBuffer.position() + length);
records.add(record);
}
return records;
}
public abstract void parse(ByteBuffer bytes);
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("PlayReadyRecord");
sb.append("{type=").append(type);
sb.append(", length=").append(getValue().limit());
// sb.append(", value=").append(Hex.encodeHex(getValue())).append('\'');
sb.append('}');
return sb.toString();
}
public abstract ByteBuffer getValue();
public static class RMHeader extends PlayReadyRecord {
String header;
public RMHeader() {
super(0x01);
}
@Override
public void parse(ByteBuffer bytes) {
try {
byte[] str = new byte[bytes.slice().limit()];
bytes.get(str);
header = new String(str, "UTF-16LE");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Override
public ByteBuffer getValue() {
byte[] headerBytes;
try {
headerBytes = header.getBytes("UTF-16LE");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return ByteBuffer.wrap(headerBytes);
}
public void setHeader(String header) {
this.header = header;
}
public String getHeader() {
return header;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("RMHeader");
sb.append("{length=").append(getValue().limit());
sb.append(", header='").append(header).append('\'');
sb.append('}');
return sb.toString();
}
}
public static class EmeddedLicenseStore extends PlayReadyRecord {
ByteBuffer value;
public EmeddedLicenseStore() {
super(0x03);
}
@Override
public void parse(ByteBuffer bytes) {
this.value = bytes.duplicate();
}
@Override
public ByteBuffer getValue() {
return value;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("EmeddedLicenseStore");
sb.append("{length=").append(getValue().limit());
//sb.append(", value='").append(Hex.encodeHex(getValue())).append('\'');
sb.append('}');
return sb.toString();
}
}
public static class DefaulPlayReadyRecord extends PlayReadyRecord {
ByteBuffer value;
public DefaulPlayReadyRecord(int type) {
super(type);
}
@Override
public void parse(ByteBuffer bytes) {
this.value = bytes.duplicate();
}
@Override
public ByteBuffer getValue() {
return value;
}
}
}
}