| <!doctype html> |
| <meta charset=utf-8> |
| <title>RTCPeerConnection H.264 profile levels</title> |
| <meta name="timeout" content="long"> |
| <script src="../third_party/sdp/sdp.js"></script> |
| <script src="simulcast.js"></script> |
| <script src="../RTCPeerConnection-helper.js"></script> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script> |
| |
| function mungeLevel(sdp, level) { |
| level_hex = Math.round(level * 10).toString(16); |
| return { |
| type: sdp.type, |
| sdp: sdp.sdp.replace(/(profile-level-id=....)(..)/g, |
| "$1" + level_hex) |
| } |
| } |
| |
| // Numbers taken from |
| // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels |
| let levelTable = { |
| 1: {mbs: 1485, fs: 99}, |
| 1.1: {mbs: 3000, fs: 396}, |
| 1.2: {mbs: 6000, fs: 396}, |
| 1.3: {mbs: 11880, fs: 396}, |
| 2: {mbs: 11880, fs: 396}, |
| 2.1: {mbs: 19800, fs: 792}, |
| 2.2: {mbs: 20250, fs: 1620}, |
| 3: {mbs: 40500, fs: 1620}, |
| 3.1: {mbs: 108000, fs: 3600}, |
| 3.2: {mbs: 216000, fs: 5120}, |
| 4: {mbs: 245760, fs: 8192}, |
| 4.1: {mbs: 245760, fs: 8192}, |
| 4.2: {mbs: 522240, fs: 8704}, |
| 5: {mbs: 589824, fs: 22800}, |
| 5.1: {mbs: 983040, fs: 36864}, |
| 5.2: {mbs: 2073600, fs: 36864}, |
| 6: {mbs: 4177920, fs: 139264}, |
| 6.1: {mbs: 8355840, fs: 139264}, |
| 6.2: {mbs: 16711680, fs: 139264}, |
| }; |
| |
| function sizeFitsLevel(width, height, fps, level) { |
| const frameSizeMacroblocks = width * height / 256; |
| const macroblocksPerSecond = frameSizeMacroblocks * fps; |
| assert_less_than_equal(frameSizeMacroblocks, |
| levelTable[level].fs, 'frame size'); |
| assert_less_than_equal(macroblocksPerSecond, |
| levelTable[level].mbs, 'macroblocks/second'); |
| } |
| |
| // Constant for now, may be variable later. |
| const framesPerSecond = 30; |
| |
| for (let level of Object.keys(levelTable)) { |
| promise_test(async t => { |
| assert_implements('getCapabilities' in RTCRtpSender, 'RTCRtpSender.getCapabilities not supported'); |
| assert_implements(RTCRtpSender.getCapabilities('video').codecs.find(c => c.mimeType === 'video/H264'), 'H264 not supported'); |
| |
| const pc1 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc1.close()); |
| const pc2 = new RTCPeerConnection(); |
| t.add_cleanup(() => pc2.close()); |
| const v = document.createElement('video'); |
| |
| // Generate the largest video we can get from the attached device. |
| // This means platform inconsistency. |
| // The fake video in Chrome WPT tests is 3840x2160. |
| const stream = await navigator.mediaDevices.getUserMedia( |
| {video: {width: 12800, height: 7200, frameRate: framesPerSecond}}); |
| t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); |
| const transceiver = pc1.addTransceiver(stream.getVideoTracks()[0], { |
| streams: [stream], |
| }); |
| preferCodec(transceiver, 'video/H264'); |
| |
| exchangeIceCandidates(pc1, pc2); |
| const trackEvent = new Promise(r => pc2.ontrack = r); |
| |
| const offer = await pc1.createOffer(); |
| await pc1.setLocalDescription(offer), |
| await pc2.setRemoteDescription(offer); |
| const answer = await pc2.createAnswer(); |
| await pc2.setLocalDescription(answer); |
| await pc1.setRemoteDescription(mungeLevel(answer, level)); |
| |
| v.srcObject = new MediaStream([(await trackEvent).track]); |
| let metadataLoaded = new Promise((resolve) => { |
| v.autoplay = true; |
| v.id = stream.id |
| v.addEventListener('loadedmetadata', () => { |
| resolve(); |
| }); |
| }); |
| await metadataLoaded; |
| // Ensure that H.264 is in fact used. |
| const statsReport = await transceiver.sender.getStats(); |
| for (const stats of statsReport.values()) { |
| if (stats.type === 'outbound-rtp') { |
| const activeCodec = stats.codecId; |
| const codecStats = statsReport.get(activeCodec); |
| assert_implements_optional(codecStats.mimeType ==='video/H264', |
| 'Level ' + level + ' H264 video is not supported'); |
| } |
| } |
| // TODO(hta): This will not catch situations where the initial size is |
| // within the permitted bounds, but resolution or framerate changes to |
| // outside the permitted bounds after a while. Should be addressed. |
| sizeFitsLevel(v.videoWidth, v.videoHeight, framesPerSecond, level); |
| }, 'Level ' + level + ' H264 video is appropriately constrained'); |
| |
| } |
| </script> |