PlayReadyHeader.java revision dd9eb897ee7c7b507cbdcf80263bb4b5de6966bf
1package com.googlecode.mp4parser.boxes.piff; 2 3import com.coremedia.iso.IsoFile; 4import com.coremedia.iso.IsoTypeReader; 5import com.coremedia.iso.IsoTypeWriter; 6import com.googlecode.mp4parser.util.Path; 7 8import java.io.FileInputStream; 9import java.io.FileNotFoundException; 10import java.io.IOException; 11import java.io.UnsupportedEncodingException; 12import java.nio.ByteBuffer; 13import java.util.ArrayList; 14import java.util.Collections; 15import java.util.List; 16 17/** 18 * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.7. ASF GUIDs 19 * <p/> 20 * <p/> 21 * ASF_Protection_System_Identifier_Object 22 * 9A04F079-9840-4286-AB92E65BE0885F95 23 * <p/> 24 * ASF_Content_Protection_System_Microsoft_PlayReady 25 * F4637010-03C3-42CD-B932B48ADF3A6A54 26 * <p/> 27 * ASF_StreamType_PlayReady_Encrypted_Command_Media 28 * 8683973A-6639-463A-ABD764F1CE3EEAE0 29 * <p/> 30 * <p/> 31 * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.5. Data Objects > 2.5.1. Payload Extension for AES in Counter Mode 32 * <p/> 33 * 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. 34 * <p/> 35 * 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. 36 * <p/> 37 * The sample ID is always stored in big-endian byte order. 38 */ 39public class PlayReadyHeader extends ProtectionSpecificHeader { 40 private long length; 41 private List<PlayReadyRecord> records; 42 43 public PlayReadyHeader() { 44 45 } 46 47 @Override 48 public void parse(ByteBuffer byteBuffer) { 49 /* 50 Length DWORD 32 51 52 PlayReady Record Count WORD 16 53 54 PlayReady Records See Text Varies 55 56 */ 57 58 length = IsoTypeReader.readUInt32BE(byteBuffer); 59 int recordCount = IsoTypeReader.readUInt16BE(byteBuffer); 60 61 records = PlayReadyRecord.createFor(byteBuffer, recordCount); 62 } 63 64 @Override 65 public ByteBuffer getData() { 66 67 int size = 4 + 2; 68 for (PlayReadyRecord record : records) { 69 size += 2 + 2; 70 size += record.getValue().rewind().limit(); 71 } 72 ByteBuffer byteBuffer = ByteBuffer.allocate(size); 73 74 IsoTypeWriter.writeUInt32BE(byteBuffer, size); 75 IsoTypeWriter.writeUInt16BE(byteBuffer, records.size()); 76 for (PlayReadyRecord record : records) { 77 IsoTypeWriter.writeUInt16BE(byteBuffer, record.type); 78 IsoTypeWriter.writeUInt16BE(byteBuffer, record.getValue().limit()); 79 ByteBuffer tmp4debug = record.getValue(); 80 byteBuffer.put(tmp4debug); 81 } 82 83 return byteBuffer; 84 } 85 86 public void setRecords(List<PlayReadyRecord> records) { 87 this.records = records; 88 } 89 90 public List<PlayReadyRecord> getRecords() { 91 return Collections.unmodifiableList(records); 92 } 93 94 @Override 95 public String toString() { 96 final StringBuilder sb = new StringBuilder(); 97 sb.append("PlayReadyHeader"); 98 sb.append("{length=").append(length); 99 sb.append(", recordCount=").append(records.size()); 100 sb.append(", records=").append(records); 101 sb.append('}'); 102 return sb.toString(); 103 } 104 105 public static abstract class PlayReadyRecord { 106 int type; 107 108 109 public PlayReadyRecord(int type) { 110 this.type = type; 111 } 112 113 public static List<PlayReadyRecord> createFor(ByteBuffer byteBuffer, int recordCount) { 114 List<PlayReadyRecord> records = new ArrayList<PlayReadyRecord>(recordCount); 115 116 for (int i = 0; i < recordCount; i++) { 117 PlayReadyRecord record; 118 int type = IsoTypeReader.readUInt16BE(byteBuffer); 119 int length = IsoTypeReader.readUInt16BE(byteBuffer); 120 switch (type) { 121 case 0x1: 122 record = new RMHeader(); 123 break; 124 case 0x2: 125 record = new DefaulPlayReadyRecord(0x02); 126 break; 127 case 0x3: 128 record = new EmeddedLicenseStore(); 129 break; 130 default: 131 record = new DefaulPlayReadyRecord(type); 132 } 133 record.parse((ByteBuffer) byteBuffer.slice().limit(length)); 134 byteBuffer.position(byteBuffer.position() + length); 135 records.add(record); 136 } 137 138 return records; 139 } 140 141 public abstract void parse(ByteBuffer bytes); 142 143 @Override 144 public String toString() { 145 final StringBuilder sb = new StringBuilder(); 146 sb.append("PlayReadyRecord"); 147 sb.append("{type=").append(type); 148 sb.append(", length=").append(getValue().limit()); 149// sb.append(", value=").append(Hex.encodeHex(getValue())).append('\''); 150 sb.append('}'); 151 return sb.toString(); 152 } 153 154 public abstract ByteBuffer getValue(); 155 156 public static class RMHeader extends PlayReadyRecord { 157 String header; 158 159 public RMHeader() { 160 super(0x01); 161 } 162 163 @Override 164 public void parse(ByteBuffer bytes) { 165 try { 166 byte[] str = new byte[bytes.slice().limit()]; 167 bytes.get(str); 168 header = new String(str, "UTF-16LE"); 169 } catch (UnsupportedEncodingException e) { 170 throw new RuntimeException(e); 171 } 172 } 173 174 @Override 175 public ByteBuffer getValue() { 176 byte[] headerBytes; 177 try { 178 headerBytes = header.getBytes("UTF-16LE"); 179 } catch (UnsupportedEncodingException e) { 180 throw new RuntimeException(e); 181 } 182 return ByteBuffer.wrap(headerBytes); 183 } 184 185 public void setHeader(String header) { 186 this.header = header; 187 } 188 189 public String getHeader() { 190 return header; 191 } 192 193 @Override 194 public String toString() { 195 final StringBuilder sb = new StringBuilder(); 196 sb.append("RMHeader"); 197 sb.append("{length=").append(getValue().limit()); 198 sb.append(", header='").append(header).append('\''); 199 sb.append('}'); 200 return sb.toString(); 201 } 202 } 203 204 public static class EmeddedLicenseStore extends PlayReadyRecord { 205 ByteBuffer value; 206 207 public EmeddedLicenseStore() { 208 super(0x03); 209 } 210 211 @Override 212 public void parse(ByteBuffer bytes) { 213 this.value = bytes.duplicate(); 214 } 215 216 @Override 217 public ByteBuffer getValue() { 218 return value; 219 } 220 221 @Override 222 public String toString() { 223 final StringBuilder sb = new StringBuilder(); 224 sb.append("EmeddedLicenseStore"); 225 sb.append("{length=").append(getValue().limit()); 226 //sb.append(", value='").append(Hex.encodeHex(getValue())).append('\''); 227 sb.append('}'); 228 return sb.toString(); 229 } 230 } 231 232 public static class DefaulPlayReadyRecord extends PlayReadyRecord { 233 ByteBuffer value; 234 235 public DefaulPlayReadyRecord(int type) { 236 super(type); 237 } 238 239 @Override 240 public void parse(ByteBuffer bytes) { 241 this.value = bytes.duplicate(); 242 } 243 244 @Override 245 public ByteBuffer getValue() { 246 return value; 247 } 248 249 } 250 251 } 252 253} 254