diff options
author | Primiano Tucci <primiano@google.com> | 2014-09-30 14:46:33 +0100 |
---|---|---|
committer | Primiano Tucci <primiano@google.com> | 2014-09-30 14:46:33 +0100 |
commit | da0509e3087cc5ee9adc0fe1abb85112ea6529a5 (patch) | |
tree | 532416c2f3025131e902ab655f3d404945485a6b | |
parent | 67bbc8e3efef31646fec91b0b422a78708a3f4aa (diff) | |
parent | 6fd722a3e5cdf933ad206ec7cd697ce95a5630cd (diff) | |
download | talk-lollipop-mr1-wfc-release.tar.gz |
Merge from Chromium at DEPS revision 267aeeb8d85candroid-cts-5.1_r9android-cts-5.1_r8android-cts-5.1_r7android-cts-5.1_r6android-cts-5.1_r5android-cts-5.1_r4android-cts-5.1_r3android-cts-5.1_r28android-cts-5.1_r27android-cts-5.1_r26android-cts-5.1_r25android-cts-5.1_r24android-cts-5.1_r23android-cts-5.1_r22android-cts-5.1_r21android-cts-5.1_r20android-cts-5.1_r2android-cts-5.1_r19android-cts-5.1_r18android-cts-5.1_r17android-cts-5.1_r16android-cts-5.1_r15android-cts-5.1_r14android-cts-5.1_r13android-cts-5.1_r10android-cts-5.1_r1android-5.1.1_r9android-5.1.1_r8android-5.1.1_r7android-5.1.1_r6android-5.1.1_r5android-5.1.1_r4android-5.1.1_r38android-5.1.1_r37android-5.1.1_r36android-5.1.1_r35android-5.1.1_r34android-5.1.1_r33android-5.1.1_r30android-5.1.1_r3android-5.1.1_r29android-5.1.1_r28android-5.1.1_r26android-5.1.1_r25android-5.1.1_r24android-5.1.1_r23android-5.1.1_r22android-5.1.1_r20android-5.1.1_r2android-5.1.1_r19android-5.1.1_r18android-5.1.1_r17android-5.1.1_r16android-5.1.1_r15android-5.1.1_r14android-5.1.1_r13android-5.1.1_r12android-5.1.1_r10android-5.1.1_r1android-5.1.0_r5android-5.1.0_r4android-5.1.0_r3android-5.1.0_r1lollipop-mr1-wfc-releaselollipop-mr1-releaselollipop-mr1-fi-releaselollipop-mr1-devlollipop-mr1-cts-release
This commit was generated by merge_to_master.py.
Change-Id: I3cccc8f04ad0036aecdb7eefe316a059ebcefaf9
203 files changed, 4517 insertions, 7657 deletions
@@ -1,14 +1,13 @@ set noparent -fischman@webrtc.org henrike@webrtc.org hta@webrtc.org +jiayl@webrtc.org juberti@webrtc.org mallinath@webrtc.org perkj@webrtc.org pthatcher@webrtc.org sergeyu@chromium.org tommi@webrtc.org -wu@webrtc.org per-file *.isolate=kjellander@webrtc.org diff --git a/PRESUBMIT.py b/PRESUBMIT.py index d447947..a7b4851 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -24,11 +24,7 @@ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # List of files that should not be committed to -DO_NOT_SUBMIT_FILES = [ - "talk/media/webrtc/webrtcmediaengine.h", - "talk/media/webrtc/webrtcvideoengine.cc", - "talk/media/webrtc/webrtcvideoengine.h", - "talk/media/webrtc/webrtcvideoengine_unittest.cc"] +DO_NOT_SUBMIT_FILES = ["talk/media/webrtc/webrtcvideoengine.cc"] def _LicenseHeader(input_api): """Returns the license header regexp.""" diff --git a/app/webrtc/java/android/org/webrtc/VideoRendererGui.java b/app/webrtc/java/android/org/webrtc/VideoRendererGui.java index 439f942..af625c0 100644 --- a/app/webrtc/java/android/org/webrtc/VideoRendererGui.java +++ b/app/webrtc/java/android/org/webrtc/VideoRendererGui.java @@ -37,6 +37,10 @@ import java.util.concurrent.LinkedBlockingQueue; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLContext; +import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.Log; @@ -54,14 +58,28 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { private static VideoRendererGui instance = null; private static final String TAG = "VideoRendererGui"; private GLSurfaceView surface; + private static EGLContext eglContext = null; // Indicates if SurfaceView.Renderer.onSurfaceCreated was called. // If true then for every newly created yuv image renderer createTexture() // should be called. The variable is accessed on multiple threads and // all accesses are synchronized on yuvImageRenderers' object lock. private boolean onSurfaceCreatedCalled; + private int screenWidth; + private int screenHeight; // List of yuv renderers. private ArrayList<YuvImageRenderer> yuvImageRenderers; - private int program; + private int yuvProgram; + private int oesProgram; + // Types of video scaling: + // SCALE_ASPECT_FIT - video frame is scaled to fit the size of the view by + // maintaining the aspect ratio (black borders may be displayed). + // SCALE_ASPECT_FILL - video frame is scaled to fill the size of the view by + // maintaining the aspect ratio. Some portion of the video frame may be + // clipped. + // SCALE_FILL - video frame is scaled to to fill the size of the view. Video + // aspect ratio is changed if necessary. + private static enum ScalingType + { SCALE_ASPECT_FIT, SCALE_ASPECT_FILL, SCALE_FILL }; private final String VERTEX_SHADER_STRING = "varying vec2 interp_tc;\n" + @@ -73,7 +91,7 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { " interp_tc = in_tc;\n" + "}\n"; - private final String FRAGMENT_SHADER_STRING = + private final String YUV_FRAGMENT_SHADER_STRING = "precision mediump float;\n" + "varying vec2 interp_tc;\n" + "\n" + @@ -91,6 +109,19 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { " y + 1.77 * u, 1);\n" + "}\n"; + + private static final String OES_FRAGMENT_SHADER_STRING = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 interp_tc;\n" + + "\n" + + "uniform samplerExternalOES oes_tex;\n" + + "\n" + + "void main() {\n" + + " gl_FragColor = texture2D(oes_tex, interp_tc);\n" + + "}\n"; + + private VideoRendererGui(GLSurfaceView surface) { this.surface = surface; // Create an OpenGL ES 2.0 context. @@ -124,23 +155,46 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { return buffer; } - // Compile & attach a |type| shader specified by |source| to |program|. - private static void addShaderTo( - int type, String source, int program) { + private int loadShader(int shaderType, String source) { int[] result = new int[] { GLES20.GL_FALSE }; - int shader = GLES20.glCreateShader(type); + int shader = GLES20.glCreateShader(shaderType); GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, - GLES20.glGetShaderInfoLog(shader) + ", source: " + source); - GLES20.glAttachShader(program, shader); - GLES20.glDeleteShader(shader); + if (result[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not compile shader " + shaderType + ":" + + GLES20.glGetShaderInfoLog(shader)); + throw new RuntimeException(GLES20.glGetShaderInfoLog(shader)); + } + checkNoGLES2Error(); + return shader; +} + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + int program = GLES20.glCreateProgram(); + if (program == 0) { + throw new RuntimeException("Could not create program"); + } + GLES20.glAttachShader(program, vertexShader); + GLES20.glAttachShader(program, fragmentShader); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[] { + GLES20.GL_FALSE + }; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: " + + GLES20.glGetProgramInfoLog(program)); + throw new RuntimeException(GLES20.glGetProgramInfoLog(program)); + } checkNoGLES2Error(); - } + return program; +} /** * Class used to display stream of YUV420 frames at particular location @@ -149,9 +203,12 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { */ private static class YuvImageRenderer implements VideoRenderer.Callbacks { private GLSurfaceView surface; - private int program; - private FloatBuffer textureVertices; + private int id; + private int yuvProgram; + private int oesProgram; private int[] yuvTextures = { -1, -1, -1 }; + private int oesTexture = -1; + private float[] stMatrix = new float[16]; // Render frame queue - accessed by two threads. renderFrame() call does // an offer (writing I420Frame to render) and early-returns (recording @@ -159,8 +216,13 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { // copies frame to texture and then removes it from a queue using poll(). LinkedBlockingQueue<I420Frame> frameToRenderQueue; // Local copy of incoming video frame. - private I420Frame frameToRender; - // Flag if renderFrame() was ever called + private I420Frame yuvFrameToRender; + private I420Frame textureFrameToRender; + // Type of video frame used for recent frame rendering. + private static enum RendererType { RENDERER_YUV, RENDERER_TEXTURE }; + private RendererType rendererType; + private ScalingType scalingType; + // Flag if renderFrame() was ever called. boolean seenFrame; // Total number of video frames received in renderFrame() call. private int framesReceived; @@ -174,40 +236,68 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { // Time in ns spent in draw() function. private long drawTimeNs; // Time in ns spent in renderFrame() function - including copying frame - // data to rendering planes + // data to rendering planes. private long copyTimeNs; - - // Texture Coordinates mapping the entire texture. - private final FloatBuffer textureCoords = directNativeFloatBuffer( - new float[] { - 0, 0, 0, 1, 1, 0, 1, 1 - }); + // Texture vertices. + private float texLeft; + private float texRight; + private float texTop; + private float texBottom; + private FloatBuffer textureVertices; + // Texture UV coordinates offsets. + private float texOffsetU; + private float texOffsetV; + private FloatBuffer textureCoords; + // Flag if texture vertices or coordinates update is needed. + private boolean updateTextureProperties; + // Viewport dimensions. + private int screenWidth; + private int screenHeight; + // Video dimension. + private int videoWidth; + private int videoHeight; private YuvImageRenderer( - GLSurfaceView surface, - int x, int y, int width, int height) { - Log.v(TAG, "YuvImageRenderer.Create"); + GLSurfaceView surface, int id, + int x, int y, int width, int height, + ScalingType scalingType) { + Log.d(TAG, "YuvImageRenderer.Create id: " + id); this.surface = surface; + this.id = id; + this.scalingType = scalingType; frameToRenderQueue = new LinkedBlockingQueue<I420Frame>(1); // Create texture vertices. - float xLeft = (x - 50) / 50.0f; - float yTop = (50 - y) / 50.0f; - float xRight = Math.min(1.0f, (x + width - 50) / 50.0f); - float yBottom = Math.max(-1.0f, (50 - y - height) / 50.0f); + texLeft = (x - 50) / 50.0f; + texTop = (50 - y) / 50.0f; + texRight = Math.min(1.0f, (x + width - 50) / 50.0f); + texBottom = Math.max(-1.0f, (50 - y - height) / 50.0f); float textureVeticesFloat[] = new float[] { - xLeft, yTop, - xLeft, yBottom, - xRight, yTop, - xRight, yBottom + texLeft, texTop, + texLeft, texBottom, + texRight, texTop, + texRight, texBottom }; textureVertices = directNativeFloatBuffer(textureVeticesFloat); + // Create texture UV coordinates. + texOffsetU = 0; + texOffsetV = 0; + float textureCoordinatesFloat[] = new float[] { + texOffsetU, texOffsetV, // left top + texOffsetU, 1.0f - texOffsetV, // left bottom + 1.0f - texOffsetU, texOffsetV, // right top + 1.0f - texOffsetU, 1.0f - texOffsetV // right bottom + }; + textureCoords = directNativeFloatBuffer(textureCoordinatesFloat); + updateTextureProperties = false; } - private void createTextures(int program) { - Log.v(TAG, " YuvImageRenderer.createTextures"); - this.program = program; + private void createTextures(int yuvProgram, int oesProgram) { + Log.d(TAG, " YuvImageRenderer.createTextures " + id + " on GL thread:" + + Thread.currentThread().getId()); + this.yuvProgram = yuvProgram; + this.oesProgram = oesProgram; - // Generate 3 texture ids for Y/U/V and place them into |textures|. + // Generate 3 texture ids for Y/U/V and place them into |yuvTextures|. GLES20.glGenTextures(3, yuvTextures, 0); for (int i = 0; i < 3; i++) { GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); @@ -226,39 +316,139 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { checkNoGLES2Error(); } + private void checkAdjustTextureCoords() { + if (!updateTextureProperties || + scalingType == ScalingType.SCALE_FILL) { + return; + } + // Re - calculate texture vertices to preserve video aspect ratio. + float texRight = this.texRight; + float texLeft = this.texLeft; + float texTop = this.texTop; + float texBottom = this.texBottom; + float displayWidth = (texRight - texLeft) * screenWidth / 2; + float displayHeight = (texTop - texBottom) * screenHeight / 2; + if (displayWidth > 1 && displayHeight > 1 && + videoWidth > 1 && videoHeight > 1) { + float displayAspectRatio = displayWidth / displayHeight; + float videoAspectRatio = (float)videoWidth / videoHeight; + if (scalingType == ScalingType.SCALE_ASPECT_FIT) { + // Need to re-adjust vertices width or height to match video AR. + if (displayAspectRatio > videoAspectRatio) { + float deltaX = (displayWidth - videoAspectRatio * displayHeight) / + instance.screenWidth; + texRight -= deltaX; + texLeft += deltaX; + } else { + float deltaY = (displayHeight - displayWidth / videoAspectRatio) / + instance.screenHeight; + texTop -= deltaY; + texBottom += deltaY; + } + // Re-allocate vertices buffer to adjust to video aspect ratio. + float textureVeticesFloat[] = new float[] { + texLeft, texTop, + texLeft, texBottom, + texRight, texTop, + texRight, texBottom + }; + textureVertices = directNativeFloatBuffer(textureVeticesFloat); + } + if (scalingType == ScalingType.SCALE_ASPECT_FILL) { + // Need to re-adjust UV coordinates to match display AR. + if (displayAspectRatio > videoAspectRatio) { + texOffsetV = (1.0f - videoAspectRatio / displayAspectRatio) / 2.0f; + } else { + texOffsetU = (1.0f - displayAspectRatio / videoAspectRatio) / 2.0f; + } + // Re-allocate coordinates buffer to adjust to display aspect ratio. + float textureCoordinatesFloat[] = new float[] { + texOffsetU, texOffsetV, // left top + texOffsetU, 1.0f - texOffsetV, // left bottom + 1.0f - texOffsetU, texOffsetV, // right top + 1.0f - texOffsetU, 1.0f - texOffsetV // right bottom + }; + textureCoords = directNativeFloatBuffer(textureCoordinatesFloat); + } + } + updateTextureProperties = false; + } + private void draw() { - long now = System.nanoTime(); if (!seenFrame) { // No frame received yet - nothing to render. return; } + // Check if texture vertices/coordinates adjustment is required when + // screen orientation changes or video frame size changes. + checkAdjustTextureCoords(); + + long now = System.nanoTime(); + I420Frame frameFromQueue; synchronized (frameToRenderQueue) { frameFromQueue = frameToRenderQueue.peek(); if (frameFromQueue != null && startTimeNs == -1) { startTimeNs = now; } - for (int i = 0; i < 3; ++i) { - int w = (i == 0) ? frameToRender.width : frameToRender.width / 2; - int h = (i == 0) ? frameToRender.height : frameToRender.height / 2; - GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + + if (rendererType == RendererType.RENDERER_YUV) { + // YUV textures rendering. + GLES20.glUseProgram(yuvProgram); + + for (int i = 0; i < 3; ++i) { + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); + if (frameFromQueue != null) { + int w = (i == 0) ? + frameFromQueue.width : frameFromQueue.width / 2; + int h = (i == 0) ? + frameFromQueue.height : frameFromQueue.height / 2; + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, + w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, + frameFromQueue.yuvPlanes[i]); + } + } + } else { + // External texture rendering. + GLES20.glUseProgram(oesProgram); + if (frameFromQueue != null) { - GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, - w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, - frameFromQueue.yuvPlanes[i]); + oesTexture = frameFromQueue.textureId; + if (frameFromQueue.textureObject instanceof SurfaceTexture) { + SurfaceTexture surfaceTexture = + (SurfaceTexture) frameFromQueue.textureObject; + surfaceTexture.updateTexImage(); + surfaceTexture.getTransformMatrix(stMatrix); + } } + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTexture); } + if (frameFromQueue != null) { frameToRenderQueue.poll(); } } - int posLocation = GLES20.glGetAttribLocation(program, "in_pos"); + + if (rendererType == RendererType.RENDERER_YUV) { + GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "y_tex"), 0); + GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "u_tex"), 1); + GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "v_tex"), 2); + } + + int posLocation = GLES20.glGetAttribLocation(yuvProgram, "in_pos"); + if (posLocation == -1) { + throw new RuntimeException("Could not get attrib location for in_pos"); + } GLES20.glEnableVertexAttribArray(posLocation); GLES20.glVertexAttribPointer( posLocation, 2, GLES20.GL_FLOAT, false, 0, textureVertices); - int texLocation = GLES20.glGetAttribLocation(program, "in_tc"); + int texLocation = GLES20.glGetAttribLocation(yuvProgram, "in_tc"); + if (texLocation == -1) { + throw new RuntimeException("Could not get attrib location for in_tc"); + } GLES20.glEnableVertexAttribArray(texLocation); GLES20.glVertexAttribPointer( texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords); @@ -281,29 +471,41 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { private void logStatistics() { long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs; - Log.v(TAG, "Frames received: " + framesReceived + ". Dropped: " + - framesDropped + ". Rendered: " + framesRendered); + Log.d(TAG, "ID: " + id + ". Type: " + rendererType + + ". Frames received: " + framesReceived + + ". Dropped: " + framesDropped + ". Rendered: " + framesRendered); if (framesReceived > 0 && framesRendered > 0) { - Log.v(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) + + Log.d(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) + " ms. FPS: " + (float)framesRendered * 1e9 / timeSinceFirstFrameNs); - Log.v(TAG, "Draw time: " + + Log.d(TAG, "Draw time: " + (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " + (int) (copyTimeNs / (1000 * framesReceived)) + " us"); } } + public void setScreenSize(final int screenWidth, final int screenHeight) { + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + updateTextureProperties = true; + } + @Override public void setSize(final int width, final int height) { - Log.v(TAG, "YuvImageRenderer.setSize: " + width + " x " + height); + Log.d(TAG, "ID: " + id + ". YuvImageRenderer.setSize: " + + width + " x " + height); + videoWidth = width; + videoHeight = height; int[] strides = { width, width / 2, width / 2 }; // Frame re-allocation need to be synchronized with copying // frame to textures in draw() function to avoid re-allocating // the frame while it is being copied. synchronized (frameToRenderQueue) { - // Clear rendering queue + // Clear rendering queue. frameToRenderQueue.poll(); - // Re-allocate / allocate the frame - frameToRender = new I420Frame(width, height, strides, null); + // Re-allocate / allocate the frame. + yuvFrameToRender = new I420Frame(width, height, strides, null); + textureFrameToRender = new I420Frame(width, height, null, -1); + updateTextureProperties = true; } } @@ -311,24 +513,26 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { public synchronized void renderFrame(I420Frame frame) { long now = System.nanoTime(); framesReceived++; - // Check input frame parameters. - if (!(frame.yuvStrides[0] == frame.width && - frame.yuvStrides[1] == frame.width / 2 && - frame.yuvStrides[2] == frame.width / 2)) { - Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " + - frame.yuvStrides[1] + ", " + frame.yuvStrides[2]); - return; - } // Skip rendering of this frame if setSize() was not called. - if (frameToRender == null) { + if (yuvFrameToRender == null || textureFrameToRender == null) { framesDropped++; return; } - // Check incoming frame dimensions - if (frame.width != frameToRender.width || - frame.height != frameToRender.height) { - throw new RuntimeException("Wrong frame size " + - frame.width + " x " + frame.height); + // Check input frame parameters. + if (frame.yuvFrame) { + if (!(frame.yuvStrides[0] == frame.width && + frame.yuvStrides[1] == frame.width / 2 && + frame.yuvStrides[2] == frame.width / 2)) { + Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " + + frame.yuvStrides[1] + ", " + frame.yuvStrides[2]); + return; + } + // Check incoming frame dimensions. + if (frame.width != yuvFrameToRender.width || + frame.height != yuvFrameToRender.height) { + throw new RuntimeException("Wrong frame size " + + frame.width + " x " + frame.height); + } } if (frameToRenderQueue.size() > 0) { @@ -336,20 +540,36 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { framesDropped++; return; } - frameToRender.copyFrom(frame); + + // Create a local copy of the frame. + if (frame.yuvFrame) { + yuvFrameToRender.copyFrom(frame); + rendererType = RendererType.RENDERER_YUV; + frameToRenderQueue.offer(yuvFrameToRender); + } else { + textureFrameToRender.copyFrom(frame); + rendererType = RendererType.RENDERER_TEXTURE; + frameToRenderQueue.offer(textureFrameToRender); + } copyTimeNs += (System.nanoTime() - now); - frameToRenderQueue.offer(frameToRender); seenFrame = true; + + // Request rendering. surface.requestRender(); } + } /** Passes GLSurfaceView to video renderer. */ public static void setView(GLSurfaceView surface) { - Log.v(TAG, "VideoRendererGui.setView"); + Log.d(TAG, "VideoRendererGui.setView"); instance = new VideoRendererGui(surface); } + public static EGLContext getEGLContext() { + return eglContext; + } + /** * Creates VideoRenderer with top left corner at (x, y) and resolution * (width, height). All parameters are in percentage of screen resolution. @@ -360,6 +580,11 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { return new VideoRenderer(javaGuiRenderer); } + public static VideoRenderer.Callbacks createGuiRenderer( + int x, int y, int width, int height) { + return create(x, y, width, height); + } + /** * Creates VideoRenderer.Callbacks with top left corner at (x, y) and * resolution (width, height). All parameters are in percentage of @@ -379,7 +604,8 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { "Attempt to create yuv renderer before setting GLSurfaceView"); } final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer( - instance.surface, x, y, width, height); + instance.surface, instance.yuvImageRenderers.size(), + x, y, width, height, ScalingType.SCALE_ASPECT_FIT); synchronized (instance.yuvImageRenderers) { if (instance.onSurfaceCreatedCalled) { // onSurfaceCreated has already been called for VideoRendererGui - @@ -388,7 +614,10 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { final CountDownLatch countDownLatch = new CountDownLatch(1); instance.surface.queueEvent(new Runnable() { public void run() { - yuvImageRenderer.createTextures(instance.program); + yuvImageRenderer.createTextures( + instance.yuvProgram, instance.oesProgram); + yuvImageRenderer.setScreenSize( + instance.screenWidth, instance.screenHeight); countDownLatch.countDown(); } }); @@ -407,43 +636,40 @@ public class VideoRendererGui implements GLSurfaceView.Renderer { @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { - Log.v(TAG, "VideoRendererGui.onSurfaceCreated"); - - // Create program. - program = GLES20.glCreateProgram(); - addShaderTo(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_STRING, program); - addShaderTo(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_STRING, program); + Log.d(TAG, "VideoRendererGui.onSurfaceCreated"); + // Store render EGL context + eglContext = EGL14.eglGetCurrentContext(); + Log.d(TAG, "VideoRendererGui EGL Context: " + eglContext); - GLES20.glLinkProgram(program); - int[] result = new int[] { - GLES20.GL_FALSE - }; - result[0] = GLES20.GL_FALSE; - GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, - GLES20.glGetProgramInfoLog(program)); - GLES20.glUseProgram(program); - - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "y_tex"), 0); - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "u_tex"), 1); - GLES20.glUniform1i(GLES20.glGetUniformLocation(program, "v_tex"), 2); + // Create YUV and OES programs. + yuvProgram = createProgram(VERTEX_SHADER_STRING, + YUV_FRAGMENT_SHADER_STRING); + oesProgram = createProgram(VERTEX_SHADER_STRING, + OES_FRAGMENT_SHADER_STRING); synchronized (yuvImageRenderers) { // Create textures for all images. for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { - yuvImageRenderer.createTextures(program); + yuvImageRenderer.createTextures(yuvProgram, oesProgram); } onSurfaceCreatedCalled = true; } checkNoGLES2Error(); - GLES20.glClearColor(0.0f, 0.0f, 0.3f, 1.0f); + GLES20.glClearColor(0.0f, 0.0f, 0.1f, 1.0f); } @Override public void onSurfaceChanged(GL10 unused, int width, int height) { - Log.v(TAG, "VideoRendererGui.onSurfaceChanged: " + + Log.d(TAG, "VideoRendererGui.onSurfaceChanged: " + width + " x " + height + " "); + screenWidth = width; + screenHeight = height; GLES20.glViewport(0, 0, width, height); + synchronized (yuvImageRenderers) { + for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { + yuvImageRenderer.setScreenSize(screenWidth, screenHeight); + } + } } @Override diff --git a/app/webrtc/java/jni/peerconnection_jni.cc b/app/webrtc/java/jni/peerconnection_jni.cc index 9b34fb5..27f69e4 100644 --- a/app/webrtc/java/jni/peerconnection_jni.cc +++ b/app/webrtc/java/jni/peerconnection_jni.cc @@ -77,23 +77,31 @@ #include "third_party/libyuv/include/libyuv/convert_from.h" #include "third_party/libyuv/include/libyuv/video_common.h" #include "webrtc/base/bind.h" +#include "webrtc/base/checks.h" #include "webrtc/base/logging.h" #include "webrtc/base/messagequeue.h" #include "webrtc/base/ssladapter.h" +#include "webrtc/common_video/interface/texture_video_frame.h" #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" #include "webrtc/system_wrappers/interface/compile_assert.h" #include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/video_engine/include/vie_base.h" #include "webrtc/voice_engine/include/voe_base.h" -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) +#include <android/log.h> +#include "webrtc/modules/video_capture/video_capture_internal.h" +#include "webrtc/modules/video_render/video_render_internal.h" #include "webrtc/system_wrappers/interface/logcat_trace_context.h" +#include "webrtc/system_wrappers/interface/tick_util.h" using webrtc::CodecSpecificInfo; using webrtc::DecodedImageCallback; using webrtc::EncodedImage; using webrtc::I420VideoFrame; using webrtc::LogcatTraceContext; using webrtc::RTPFragmentationHeader; +using webrtc::TextureVideoFrame; +using webrtc::TickTime; using webrtc::VideoCodec; #endif @@ -111,6 +119,7 @@ using webrtc::DataChannelInit; using webrtc::DataChannelInterface; using webrtc::DataChannelObserver; using webrtc::IceCandidateInterface; +using webrtc::NativeHandle; using webrtc::MediaConstraintsInterface; using webrtc::MediaSourceInterface; using webrtc::MediaStreamInterface; @@ -128,33 +137,18 @@ using webrtc::VideoTrackInterface; using webrtc::VideoTrackVector; using webrtc::kVideoCodecVP8; -// Abort the process if |x| is false, emitting |msg|. -#define CHECK(x, msg) \ - if (x) {} else { \ - LOG(LS_ERROR) << __FILE__ << ":" << __LINE__ << ": " << msg; \ - abort(); \ - } -// Abort the process if |jni| has a Java exception pending, emitting |msg|. -#define CHECK_EXCEPTION(jni, msg) \ - if (0) {} else { \ - if (jni->ExceptionCheck()) { \ - jni->ExceptionDescribe(); \ - jni->ExceptionClear(); \ - CHECK(0, msg); \ - } \ - } - -// Helper that calls ptr->Release() and logs a useful message if that didn't -// actually delete *ptr because of extra refcounts. -#define CHECK_RELEASE(ptr) \ - do { \ - int count = (ptr)->Release(); \ - if (count != 0) { \ - LOG(LS_ERROR) << "Refcount unexpectedly not 0: " << (ptr) \ - << ": " << count; \ - } \ - CHECK(!count, "Unexpected refcount"); \ - } while (0) +// Abort the process if |jni| has a Java exception pending. +// This macros uses the comma operator to execute ExceptionDescribe +// and ExceptionClear ignoring their return values and sending "" +// to the error stream. +#define CHECK_EXCEPTION(jni) \ + CHECK(!jni->ExceptionCheck()) \ + << (jni->ExceptionDescribe(), jni->ExceptionClear(), "") + +// Helper that calls ptr->Release() and aborts the process with a useful +// message if that didn't actually delete *ptr because of extra refcounts. +#define CHECK_RELEASE(ptr) \ + CHECK_EQ(0, (ptr)->Release()) << "Unexpected refcount." namespace { @@ -166,18 +160,25 @@ static pthread_once_t g_jni_ptr_once = PTHREAD_ONCE_INIT; // were attached by the JVM because of a Java->native call. static pthread_key_t g_jni_ptr; +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) +// Set in PeerConnectionFactory_initializeAndroidGlobals(). +static bool factory_static_initialized = false; +#endif + + // Return thread ID as a string. static std::string GetThreadId() { char buf[21]; // Big enough to hold a kuint64max plus terminating NULL. - CHECK(snprintf(buf, sizeof(buf), "%llu", syscall(__NR_gettid)) <= sizeof(buf), - "Thread id is bigger than uint64??"); + CHECK_LT(snprintf(buf, sizeof(buf), "%llu", syscall(__NR_gettid)), + sizeof(buf)) + << "Thread id is bigger than uint64??"; return std::string(buf); } // Return the current thread's name. static std::string GetThreadName() { char name[17]; - CHECK(prctl(PR_GET_NAME, name) == 0, "prctl(PR_GET_NAME) failed"); + CHECK_EQ(0, prctl(PR_GET_NAME, name)) << "prctl(PR_GET_NAME) failed"; name[16] = '\0'; return std::string(name); } @@ -187,8 +188,8 @@ static JNIEnv* GetEnv() { void* env = NULL; jint status = g_jvm->GetEnv(&env, JNI_VERSION_1_6); CHECK(((env != NULL) && (status == JNI_OK)) || - ((env == NULL) && (status == JNI_EDETACHED)), - "Unexpected GetEnv return: " << status << ":" << env); + ((env == NULL) && (status == JNI_EDETACHED))) + << "Unexpected GetEnv return: " << status << ":" << env; return reinterpret_cast<JNIEnv*>(env); } @@ -203,16 +204,16 @@ static void ThreadDestructor(void* prev_jni_ptr) { if (!GetEnv()) return; - CHECK(GetEnv() == prev_jni_ptr, - "Detaching from another thread: " << prev_jni_ptr << ":" << GetEnv()); + CHECK(GetEnv() == prev_jni_ptr) + << "Detaching from another thread: " << prev_jni_ptr << ":" << GetEnv(); jint status = g_jvm->DetachCurrentThread(); - CHECK(status == JNI_OK, "Failed to detach thread: " << status); - CHECK(!GetEnv(), "Detaching was a successful no-op???"); + CHECK(status == JNI_OK) << "Failed to detach thread: " << status; + CHECK(!GetEnv()) << "Detaching was a successful no-op???"; } static void CreateJNIPtrKey() { - CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor), - "pthread_key_create"); + CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor)) + << "pthread_key_create"; } // Return a |JNIEnv*| usable on this thread. Attaches to |g_jvm| if necessary. @@ -220,7 +221,8 @@ static JNIEnv* AttachCurrentThreadIfNeeded() { JNIEnv* jni = GetEnv(); if (jni) return jni; - CHECK(!pthread_getspecific(g_jni_ptr), "TLS has a JNIEnv* but not attached?"); + CHECK(!pthread_getspecific(g_jni_ptr)) + << "TLS has a JNIEnv* but not attached?"; char* name = strdup((GetThreadName() + " - " + GetThreadId()).c_str()); JavaVMAttachArgs args; @@ -233,11 +235,11 @@ static JNIEnv* AttachCurrentThreadIfNeeded() { #else JNIEnv* env = NULL; #endif - CHECK(!g_jvm->AttachCurrentThread(&env, &args), "Failed to attach thread"); + CHECK(!g_jvm->AttachCurrentThread(&env, &args)) << "Failed to attach thread"; free(name); - CHECK(env, "AttachCurrentThread handed back NULL!"); + CHECK(env) << "AttachCurrentThread handed back NULL!"; jni = reinterpret_cast<JNIEnv*>(env); - CHECK(!pthread_setspecific(g_jni_ptr, jni), "pthread_setspecific"); + CHECK(!pthread_setspecific(g_jni_ptr, jni)) << "pthread_setspecific"; return jni; } @@ -269,10 +271,13 @@ class ClassReferenceHolder { LoadClass(jni, "org/webrtc/DataChannel$Init"); LoadClass(jni, "org/webrtc/DataChannel$State"); LoadClass(jni, "org/webrtc/IceCandidate"); -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) + LoadClass(jni, "android/graphics/SurfaceTexture"); + LoadClass(jni, "android/opengl/EGLContext"); LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder"); LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo"); LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder"); + LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo"); #endif LoadClass(jni, "org/webrtc/MediaSource$State"); LoadClass(jni, "org/webrtc/MediaStream"); @@ -289,7 +294,7 @@ class ClassReferenceHolder { } ~ClassReferenceHolder() { - CHECK(classes_.empty(), "Must call FreeReferences() before dtor!"); + CHECK(classes_.empty()) << "Must call FreeReferences() before dtor!"; } void FreeReferences(JNIEnv* jni) { @@ -302,20 +307,20 @@ class ClassReferenceHolder { jclass GetClass(const std::string& name) { std::map<std::string, jclass>::iterator it = classes_.find(name); - CHECK(it != classes_.end(), "Unexpected GetClass() call for: " << name); + CHECK(it != classes_.end()) << "Unexpected GetClass() call for: " << name; return it->second; } private: void LoadClass(JNIEnv* jni, const std::string& name) { jclass localRef = jni->FindClass(name.c_str()); - CHECK_EXCEPTION(jni, "error during FindClass: " << name); - CHECK(localRef, name); + CHECK_EXCEPTION(jni) << "error during FindClass: " << name; + CHECK(localRef) << name; jclass globalRef = reinterpret_cast<jclass>(jni->NewGlobalRef(localRef)); - CHECK_EXCEPTION(jni, "error during NewGlobalRef: " << name); - CHECK(globalRef, name); + CHECK_EXCEPTION(jni) << "error during NewGlobalRef: " << name; + CHECK(globalRef) << name; bool inserted = classes_.insert(std::make_pair(name, globalRef)).second; - CHECK(inserted, "Duplicate class name: " << name); + CHECK(inserted) << "Duplicate class name: " << name; } std::map<std::string, jclass> classes_; @@ -329,27 +334,26 @@ static ClassReferenceHolder* g_class_reference_holder = NULL; jmethodID GetMethodID( JNIEnv* jni, jclass c, const std::string& name, const char* signature) { jmethodID m = jni->GetMethodID(c, name.c_str(), signature); - CHECK_EXCEPTION(jni, - "error during GetMethodID: " << name << ", " << signature); - CHECK(m, name << ", " << signature); + CHECK_EXCEPTION(jni) << "error during GetMethodID: " << name << ", " + << signature; + CHECK(m) << name << ", " << signature; return m; } jmethodID GetStaticMethodID( JNIEnv* jni, jclass c, const char* name, const char* signature) { jmethodID m = jni->GetStaticMethodID(c, name, signature); - CHECK_EXCEPTION(jni, - "error during GetStaticMethodID: " - << name << ", " << signature); - CHECK(m, name << ", " << signature); + CHECK_EXCEPTION(jni) << "error during GetStaticMethodID: " << name << ", " + << signature; + CHECK(m) << name << ", " << signature; return m; } jfieldID GetFieldID( JNIEnv* jni, jclass c, const char* name, const char* signature) { jfieldID f = jni->GetFieldID(c, name, signature); - CHECK_EXCEPTION(jni, "error during GetFieldID"); - CHECK(f, name << ", " << signature); + CHECK_EXCEPTION(jni) << "error during GetFieldID"; + CHECK(f) << name << ", " << signature; return f; } @@ -361,15 +365,15 @@ jclass FindClass(JNIEnv* jni, const char* name) { jclass GetObjectClass(JNIEnv* jni, jobject object) { jclass c = jni->GetObjectClass(object); - CHECK_EXCEPTION(jni, "error during GetObjectClass"); - CHECK(c, ""); + CHECK_EXCEPTION(jni) << "error during GetObjectClass"; + CHECK(c) << "GetObjectClass returned NULL"; return c; } jobject GetObjectField(JNIEnv* jni, jobject object, jfieldID id) { jobject o = jni->GetObjectField(object, id); - CHECK_EXCEPTION(jni, "error during GetObjectField"); - CHECK(o, ""); + CHECK_EXCEPTION(jni) << "error during GetObjectField"; + CHECK(o) << "GetObjectField returned NULL"; return o; } @@ -379,32 +383,32 @@ jstring GetStringField(JNIEnv* jni, jobject object, jfieldID id) { jlong GetLongField(JNIEnv* jni, jobject object, jfieldID id) { jlong l = jni->GetLongField(object, id); - CHECK_EXCEPTION(jni, "error during GetLongField"); + CHECK_EXCEPTION(jni) << "error during GetLongField"; return l; } jint GetIntField(JNIEnv* jni, jobject object, jfieldID id) { jint i = jni->GetIntField(object, id); - CHECK_EXCEPTION(jni, "error during GetIntField"); + CHECK_EXCEPTION(jni) << "error during GetIntField"; return i; } bool GetBooleanField(JNIEnv* jni, jobject object, jfieldID id) { jboolean b = jni->GetBooleanField(object, id); - CHECK_EXCEPTION(jni, "error during GetBooleanField"); + CHECK_EXCEPTION(jni) << "error during GetBooleanField"; return b; } jobject NewGlobalRef(JNIEnv* jni, jobject o) { jobject ret = jni->NewGlobalRef(o); - CHECK_EXCEPTION(jni, "error during NewGlobalRef"); - CHECK(ret, ""); + CHECK_EXCEPTION(jni) << "error during NewGlobalRef"; + CHECK(ret); return ret; } void DeleteGlobalRef(JNIEnv* jni, jobject o) { jni->DeleteGlobalRef(o); - CHECK_EXCEPTION(jni, "error during DeleteGlobalRef"); + CHECK_EXCEPTION(jni) << "error during DeleteGlobalRef"; } // Given a jweak reference, allocate a (strong) local reference scoped to the @@ -414,12 +418,12 @@ class WeakRef { public: WeakRef(JNIEnv* jni, jweak ref) : jni_(jni), obj_(jni_->NewLocalRef(ref)) { - CHECK_EXCEPTION(jni, "error during NewLocalRef"); + CHECK_EXCEPTION(jni) << "error during NewLocalRef"; } ~WeakRef() { if (obj_) { jni_->DeleteLocalRef(obj_); - CHECK_EXCEPTION(jni_, "error during DeleteLocalRef"); + CHECK_EXCEPTION(jni_) << "error during DeleteLocalRef"; } } jobject obj() { return obj_; } @@ -435,7 +439,7 @@ class WeakRef { class ScopedLocalRefFrame { public: explicit ScopedLocalRefFrame(JNIEnv* jni) : jni_(jni) { - CHECK(!jni_->PushLocalFrame(0), "Failed to PushLocalFrame"); + CHECK(!jni_->PushLocalFrame(0)) << "Failed to PushLocalFrame"; } ~ScopedLocalRefFrame() { jni_->PopLocalFrame(NULL); @@ -478,9 +482,9 @@ jobject JavaEnumFromIndex( jni, state_class, "values", ("()[L" + state_class_name + ";").c_str()); jobjectArray state_values = static_cast<jobjectArray>( jni->CallStaticObjectMethod(state_class, state_values_id)); - CHECK_EXCEPTION(jni, "error during CallStaticObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallStaticObjectMethod"; jobject ret = jni->GetObjectArrayElement(state_values, index); - CHECK_EXCEPTION(jni, "error during GetObjectArrayElement"); + CHECK_EXCEPTION(jni) << "error during GetObjectArrayElement"; return ret; } @@ -488,18 +492,18 @@ jobject JavaEnumFromIndex( static jstring JavaStringFromStdString(JNIEnv* jni, const std::string& native) { UnicodeString ustr(UnicodeString::fromUTF8(native)); jstring jstr = jni->NewString(ustr.getBuffer(), ustr.length()); - CHECK_EXCEPTION(jni, "error during NewString"); + CHECK_EXCEPTION(jni) << "error during NewString"; return jstr; } // Given a (UTF-16) jstring return a new UTF-8 native string. static std::string JavaToStdString(JNIEnv* jni, const jstring& j_string) { const jchar* jchars = jni->GetStringChars(j_string, NULL); - CHECK_EXCEPTION(jni, "Error during GetStringChars"); + CHECK_EXCEPTION(jni) << "Error during GetStringChars"; UnicodeString ustr(jchars, jni->GetStringLength(j_string)); - CHECK_EXCEPTION(jni, "Error during GetStringLength"); + CHECK_EXCEPTION(jni) << "Error during GetStringLength"; jni->ReleaseStringChars(j_string, jchars); - CHECK_EXCEPTION(jni, "Error during ReleaseStringChars"); + CHECK_EXCEPTION(jni) << "Error during ReleaseStringChars"; std::string ret; return ustr.toUTF8String(ret); } @@ -559,7 +563,7 @@ class PCOJava : public PeerConnectionObserver { virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); std::string sdp; - CHECK(candidate->ToString(&sdp), "got so far: " << sdp); + CHECK(candidate->ToString(&sdp)) << "got so far: " << sdp; jclass candidate_class = FindClass(jni(), "org/webrtc/IceCandidate"); jmethodID ctor = GetMethodID(jni(), candidate_class, "<init>", "(Ljava/lang/String;ILjava/lang/String;)V"); @@ -567,18 +571,18 @@ class PCOJava : public PeerConnectionObserver { jstring j_sdp = JavaStringFromStdString(jni(), sdp); jobject j_candidate = jni()->NewObject( candidate_class, ctor, j_mid, candidate->sdp_mline_index(), j_sdp); - CHECK_EXCEPTION(jni(), "error during NewObject"); + CHECK_EXCEPTION(jni()) << "error during NewObject"; jmethodID m = GetMethodID(jni(), *j_observer_class_, "onIceCandidate", "(Lorg/webrtc/IceCandidate;)V"); jni()->CallVoidMethod(*j_observer_global_, m, j_candidate); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnError() OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); jmethodID m = GetMethodID(jni(), *j_observer_class_, "onError", "()V"); jni()->CallVoidMethod(*j_observer_global_, m); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnSignalingChange( @@ -590,7 +594,7 @@ class PCOJava : public PeerConnectionObserver { jobject new_state_enum = JavaEnumFromIndex(jni(), "PeerConnection$SignalingState", new_state); jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnIceConnectionChange( @@ -602,7 +606,7 @@ class PCOJava : public PeerConnectionObserver { jobject new_state_enum = JavaEnumFromIndex( jni(), "PeerConnection$IceConnectionState", new_state); jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnIceGatheringChange( @@ -614,14 +618,14 @@ class PCOJava : public PeerConnectionObserver { jobject new_state_enum = JavaEnumFromIndex( jni(), "PeerConnection$IceGatheringState", new_state); jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnAddStream(MediaStreamInterface* stream) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); jobject j_stream = jni()->NewObject( *j_media_stream_class_, j_media_stream_ctor_, (jlong)stream); - CHECK_EXCEPTION(jni(), "error during NewObject"); + CHECK_EXCEPTION(jni()) << "error during NewObject"; AudioTrackVector audio_tracks = stream->GetAudioTracks(); for (size_t i = 0; i < audio_tracks.size(); ++i) { @@ -629,7 +633,7 @@ class PCOJava : public PeerConnectionObserver { jstring id = JavaStringFromStdString(jni(), track->id()); jobject j_track = jni()->NewObject( *j_audio_track_class_, j_audio_track_ctor_, (jlong)track, id); - CHECK_EXCEPTION(jni(), "error during NewObject"); + CHECK_EXCEPTION(jni()) << "error during NewObject"; jfieldID audio_tracks_id = GetFieldID(jni(), *j_media_stream_class_, "audioTracks", @@ -640,8 +644,8 @@ class PCOJava : public PeerConnectionObserver { "add", "(Ljava/lang/Object;)Z"); jboolean added = jni()->CallBooleanMethod(audio_tracks, add, j_track); - CHECK_EXCEPTION(jni(), "error during CallBooleanMethod"); - CHECK(added, ""); + CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod"; + CHECK(added); } VideoTrackVector video_tracks = stream->GetVideoTracks(); @@ -650,7 +654,7 @@ class PCOJava : public PeerConnectionObserver { jstring id = JavaStringFromStdString(jni(), track->id()); jobject j_track = jni()->NewObject( *j_video_track_class_, j_video_track_ctor_, (jlong)track, id); - CHECK_EXCEPTION(jni(), "error during NewObject"); + CHECK_EXCEPTION(jni()) << "error during NewObject"; jfieldID video_tracks_id = GetFieldID(jni(), *j_media_stream_class_, "videoTracks", @@ -661,22 +665,22 @@ class PCOJava : public PeerConnectionObserver { "add", "(Ljava/lang/Object;)Z"); jboolean added = jni()->CallBooleanMethod(video_tracks, add, j_track); - CHECK_EXCEPTION(jni(), "error during CallBooleanMethod"); - CHECK(added, ""); + CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod"; + CHECK(added); } streams_[stream] = jni()->NewWeakGlobalRef(j_stream); - CHECK_EXCEPTION(jni(), "error during NewWeakGlobalRef"); + CHECK_EXCEPTION(jni()) << "error during NewWeakGlobalRef"; jmethodID m = GetMethodID(jni(), *j_observer_class_, "onAddStream", "(Lorg/webrtc/MediaStream;)V"); jni()->CallVoidMethod(*j_observer_global_, m, j_stream); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnRemoveStream(MediaStreamInterface* stream) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); NativeToJavaStreamsMap::iterator it = streams_.find(stream); - CHECK(it != streams_.end(), "unexpected stream: " << std::hex << stream); + CHECK(it != streams_.end()) << "unexpected stream: " << std::hex << stream; WeakRef s(jni(), it->second); streams_.erase(it); @@ -686,14 +690,14 @@ class PCOJava : public PeerConnectionObserver { jmethodID m = GetMethodID(jni(), *j_observer_class_, "onRemoveStream", "(Lorg/webrtc/MediaStream;)V"); jni()->CallVoidMethod(*j_observer_global_, m, s.obj()); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnDataChannel(DataChannelInterface* channel) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); jobject j_channel = jni()->NewObject( *j_data_channel_class_, j_data_channel_ctor_, (jlong)channel); - CHECK_EXCEPTION(jni(), "error during NewObject"); + CHECK_EXCEPTION(jni()) << "error during NewObject"; jmethodID m = GetMethodID(jni(), *j_observer_class_, "onDataChannel", "(Lorg/webrtc/DataChannel;)V"); @@ -704,9 +708,9 @@ class PCOJava : public PeerConnectionObserver { // CallVoidMethod above as Java code might call back into native code and be // surprised to see a refcount of 2. int bumped_count = channel->AddRef(); - CHECK(bumped_count == 2, "Unexpected refcount OnDataChannel"); + CHECK(bumped_count == 2) << "Unexpected refcount OnDataChannel"; - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnRenegotiationNeeded() OVERRIDE { @@ -714,11 +718,11 @@ class PCOJava : public PeerConnectionObserver { jmethodID m = GetMethodID(jni(), *j_observer_class_, "onRenegotiationNeeded", "()V"); jni()->CallVoidMethod(*j_observer_global_, m); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } void SetConstraints(ConstraintsWrapper* constraints) { - CHECK(!constraints_.get(), "constraints already set!"); + CHECK(!constraints_.get()) << "constraints already set!"; constraints_.reset(constraints); } @@ -777,29 +781,29 @@ class ConstraintsWrapper : public MediaConstraintsInterface { jmethodID j_iterator_id = GetMethodID(jni, GetObjectClass(jni, j_list), "iterator", "()Ljava/util/Iterator;"); jobject j_iterator = jni->CallObjectMethod(j_list, j_iterator_id); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jmethodID j_has_next = GetMethodID(jni, GetObjectClass(jni, j_iterator), "hasNext", "()Z"); jmethodID j_next = GetMethodID(jni, GetObjectClass(jni, j_iterator), "next", "()Ljava/lang/Object;"); while (jni->CallBooleanMethod(j_iterator, j_has_next)) { - CHECK_EXCEPTION(jni, "error during CallBooleanMethod"); + CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; jobject entry = jni->CallObjectMethod(j_iterator, j_next); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jmethodID get_key = GetMethodID(jni, GetObjectClass(jni, entry), "getKey", "()Ljava/lang/String;"); jstring j_key = reinterpret_cast<jstring>( jni->CallObjectMethod(entry, get_key)); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jmethodID get_value = GetMethodID(jni, GetObjectClass(jni, entry), "getValue", "()Ljava/lang/String;"); jstring j_value = reinterpret_cast<jstring>( jni->CallObjectMethod(entry, get_value)); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; field->push_back(Constraint(JavaToStdString(jni, j_key), JavaToStdString(jni, j_value))); } - CHECK_EXCEPTION(jni, "error during CallBooleanMethod"); + CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; } Constraints mandatory_; @@ -809,7 +813,7 @@ class ConstraintsWrapper : public MediaConstraintsInterface { static jobject JavaSdpFromNativeSdp( JNIEnv* jni, const SessionDescriptionInterface* desc) { std::string sdp; - CHECK(desc->ToString(&sdp), "got so far: " << sdp); + CHECK(desc->ToString(&sdp)) << "got so far: " << sdp; jstring j_description = JavaStringFromStdString(jni, sdp); jclass j_type_class = FindClass( @@ -820,7 +824,7 @@ static jobject JavaSdpFromNativeSdp( jstring j_type_string = JavaStringFromStdString(jni, desc->type()); jobject j_type = jni->CallStaticObjectMethod( j_type_class, j_type_from_canonical, j_type_string); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jclass j_sdp_class = FindClass(jni, "org/webrtc/SessionDescription"); jmethodID j_sdp_ctor = GetMethodID( @@ -828,7 +832,7 @@ static jobject JavaSdpFromNativeSdp( "(Lorg/webrtc/SessionDescription$Type;Ljava/lang/String;)V"); jobject j_sdp = jni->NewObject( j_sdp_class, j_sdp_ctor, j_type, j_description); - CHECK_EXCEPTION(jni, "error during NewObject"); + CHECK_EXCEPTION(jni) << "error during NewObject"; return j_sdp; } @@ -849,7 +853,7 @@ class SdpObserverWrapper : public T { ScopedLocalRefFrame local_ref_frame(jni()); jmethodID m = GetMethodID(jni(), *j_observer_class_, "onSetSuccess", "()V"); jni()->CallVoidMethod(*j_observer_global_, m); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } // Can't mark OVERRIDE because of templating. @@ -860,7 +864,7 @@ class SdpObserverWrapper : public T { "(Lorg/webrtc/SessionDescription;)V"); jobject j_sdp = JavaSdpFromNativeSdp(jni(), desc); jni()->CallVoidMethod(*j_observer_global_, m, j_sdp); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } protected: @@ -871,7 +875,7 @@ class SdpObserverWrapper : public T { "(Ljava/lang/String;)V"); jstring j_error_string = JavaStringFromStdString(jni(), error); jni()->CallVoidMethod(*j_observer_global_, m, j_error_string); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } JNIEnv* jni() { @@ -917,11 +921,11 @@ class DataChannelObserverWrapper : public DataChannelObserver { DataChannelObserverWrapper(JNIEnv* jni, jobject j_observer) : j_observer_global_(jni, j_observer), j_observer_class_(jni, GetObjectClass(jni, j_observer)), + j_buffer_class_(jni, FindClass(jni, "org/webrtc/DataChannel$Buffer")), j_on_state_change_mid_(GetMethodID(jni, *j_observer_class_, "onStateChange", "()V")), j_on_message_mid_(GetMethodID(jni, *j_observer_class_, "onMessage", "(Lorg/webrtc/DataChannel$Buffer;)V")), - j_buffer_class_(jni, FindClass(jni, "org/webrtc/DataChannel$Buffer")), j_buffer_ctor_(GetMethodID(jni, *j_buffer_class_, "<init>", "(Ljava/nio/ByteBuffer;Z)V")) { } @@ -931,7 +935,7 @@ class DataChannelObserverWrapper : public DataChannelObserver { virtual void OnStateChange() OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); jni()->CallVoidMethod(*j_observer_global_, j_on_state_change_mid_); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } virtual void OnMessage(const DataBuffer& buffer) OVERRIDE { @@ -942,7 +946,7 @@ class DataChannelObserverWrapper : public DataChannelObserver { jobject j_buffer = jni()->NewObject(*j_buffer_class_, j_buffer_ctor_, byte_buffer, buffer.binary); jni()->CallVoidMethod(*j_observer_global_, j_on_message_mid_, j_buffer); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } private: @@ -985,7 +989,7 @@ class StatsObserverWrapper : public StatsObserver { jmethodID m = GetMethodID(jni(), *j_observer_class_, "onComplete", "([Lorg/webrtc/StatsReport;)V"); jni()->CallVoidMethod(*j_observer_global_, m, j_reports); - CHECK_EXCEPTION(jni(), "error during CallVoidMethod"); + CHECK_EXCEPTION(jni()) << "error during CallVoidMethod"; } private: @@ -1067,6 +1071,38 @@ class VideoRendererWrapper : public VideoRendererInterface { scoped_ptr<cricket::VideoRenderer> renderer_; }; +// Wrapper for texture object in TextureVideoFrame. +class NativeHandleImpl : public NativeHandle { + public: + NativeHandleImpl() : + ref_count_(0), texture_object_(NULL), texture_id_(-1) {} + virtual ~NativeHandleImpl() {} + virtual int32_t AddRef() { + return ++ref_count_; + } + virtual int32_t Release() { + return --ref_count_; + } + virtual void* GetHandle() { + return texture_object_; + } + int GetTextureId() { + return texture_id_; + } + void SetTextureObject(void *texture_object, int texture_id) { + texture_object_ = reinterpret_cast<jobject>(texture_object); + texture_id_ = texture_id; + } + int32_t ref_count() { + return ref_count_; + } + + private: + int32_t ref_count_; + jobject texture_object_; + int32_t texture_id_; +}; + // Wrapper dispatching webrtc::VideoRendererInterface to a Java VideoRenderer // instance. class JavaVideoRendererWrapper : public VideoRendererInterface { @@ -1080,10 +1116,13 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { "(Lorg/webrtc/VideoRenderer$I420Frame;)V")), j_frame_class_(jni, FindClass(jni, "org/webrtc/VideoRenderer$I420Frame")), - j_frame_ctor_id_(GetMethodID( + j_i420_frame_ctor_id_(GetMethodID( jni, *j_frame_class_, "<init>", "(II[I[Ljava/nio/ByteBuffer;)V")), + j_texture_frame_ctor_id_(GetMethodID( + jni, *j_frame_class_, "<init>", + "(IILjava/lang/Object;I)V")), j_byte_buffer_class_(jni, FindClass(jni, "java/nio/ByteBuffer")) { - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); } virtual ~JavaVideoRendererWrapper() {} @@ -1091,19 +1130,25 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { virtual void SetSize(int width, int height) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); jni()->CallVoidMethod(*j_callbacks_, j_set_size_id_, width, height); - CHECK_EXCEPTION(jni(), ""); + CHECK_EXCEPTION(jni()); } virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE { ScopedLocalRefFrame local_ref_frame(jni()); - jobject j_frame = CricketToJavaFrame(frame); - jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, j_frame); - CHECK_EXCEPTION(jni(), ""); + if (frame->GetNativeHandle() != NULL) { + jobject j_frame = CricketToJavaTextureFrame(frame); + jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, j_frame); + CHECK_EXCEPTION(jni()); + } else { + jobject j_frame = CricketToJavaI420Frame(frame); + jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, j_frame); + CHECK_EXCEPTION(jni()); + } } private: // Return a VideoRenderer.I420Frame referring to the data in |frame|. - jobject CricketToJavaFrame(const cricket::VideoFrame* frame) { + jobject CricketToJavaI420Frame(const cricket::VideoFrame* frame) { jintArray strides = jni()->NewIntArray(3); jint* strides_array = jni()->GetIntArrayElements(strides, NULL); strides_array[0] = frame->GetYPitch(); @@ -1122,10 +1167,21 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { jni()->SetObjectArrayElement(planes, 1, u_buffer); jni()->SetObjectArrayElement(planes, 2, v_buffer); return jni()->NewObject( - *j_frame_class_, j_frame_ctor_id_, + *j_frame_class_, j_i420_frame_ctor_id_, frame->GetWidth(), frame->GetHeight(), strides, planes); } + // Return a VideoRenderer.I420Frame referring texture object in |frame|. + jobject CricketToJavaTextureFrame(const cricket::VideoFrame* frame) { + NativeHandleImpl* handle = + reinterpret_cast<NativeHandleImpl*>(frame->GetNativeHandle()); + jobject texture_object = reinterpret_cast<jobject>(handle->GetHandle()); + int texture_id = handle->GetTextureId(); + return jni()->NewObject( + *j_frame_class_, j_texture_frame_ctor_id_, + frame->GetWidth(), frame->GetHeight(), texture_object, texture_id); + } + JNIEnv* jni() { return AttachCurrentThreadIfNeeded(); } @@ -1134,16 +1190,16 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { jmethodID j_set_size_id_; jmethodID j_render_frame_id_; ScopedGlobalRef<jclass> j_frame_class_; - jmethodID j_frame_ctor_id_; + jmethodID j_i420_frame_ctor_id_; + jmethodID j_texture_frame_ctor_id_; ScopedGlobalRef<jclass> j_byte_buffer_class_; }; -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) // TODO(fischman): consider pulling MediaCodecVideoEncoder out of this file and // into its own .h/.cc pair, if/when the JNI helper stuff above is extracted // from this file. -#include <android/log.h> //#define TRACK_BUFFER_TIMING #define TAG "MediaCodecVideo" #ifdef TRACK_BUFFER_TIMING @@ -1169,6 +1225,14 @@ enum COLOR_FORMATTYPE { // Arbitrary interval to poll the codec for new outputs. enum { kMediaCodecPollMs = 10 }; +// Media codec maximum output buffer ready timeout. +enum { kMediaCodecTimeoutMs = 500 }; +// Interval to print codec statistics (bitrate, fps, encoding/decoding time). +enum { kMediaCodecStatisticsIntervalMs = 3000 }; + +static int64_t GetCurrentTimeMs() { + return TickTime::Now().Ticks() / 1000000LL; +} // MediaCodecVideoEncoder is a webrtc::VideoEncoder implementation that uses // Android's MediaCodec SDK API behind the scenes to implement (hopefully) @@ -1224,9 +1288,6 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, int32_t ReleaseOnCodecThread(); int32_t SetRatesOnCodecThread(uint32_t new_bit_rate, uint32_t frame_rate); - // Reset parameters valid between InitEncode() & Release() (see below). - void ResetParameters(JNIEnv* jni); - // Helper accessors for MediaCodecVideoEncoder$OutputBufferInfo members. int GetOutputBufferInfoIndex(JNIEnv* jni, jobject j_output_buffer_info); jobject GetOutputBufferInfoBuffer(JNIEnv* jni, jobject j_output_buffer_info); @@ -1269,11 +1330,21 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, enum libyuv::FourCC encoder_fourcc_; // Encoder color space format. int last_set_bitrate_kbps_; // Last-requested bitrate in kbps. int last_set_fps_; // Last-requested frame rate. - int frames_received_; // Number of frames received by encoder. - int frames_dropped_; // Number of frames dropped by encoder. - int frames_in_queue_; // Number of frames in encoder queue. - int64_t last_input_timestamp_ms_; // Timestamp of last received yuv frame. - int64_t last_output_timestamp_ms_; // Timestamp of last encoded frame. + int64_t current_timestamp_us_; // Current frame timestamps in us. + int frames_received_; // Number of frames received by encoder. + int frames_dropped_; // Number of frames dropped by encoder. + int frames_resolution_update_; // Number of frames with new codec resolution. + int frames_in_queue_; // Number of frames in encoder queue. + int64_t start_time_ms_; // Start time for statistics. + int current_frames_; // Number of frames in the current statistics interval. + int current_bytes_; // Encoded bytes in the current statistics interval. + int current_encoding_time_ms_; // Overall encoding time in the current second + int64_t last_input_timestamp_ms_; // Timestamp of last received yuv frame. + int64_t last_output_timestamp_ms_; // Timestamp of last encoded frame. + std::vector<int32_t> timestamps_; // Video frames timestamp queue. + std::vector<int64_t> render_times_ms_; // Video frames render time queue. + std::vector<int64_t> frame_rtc_times_ms_; // Time when video frame is sent to + // encoder input. // Frame size in bytes fed to MediaCodec. int yuv_size_; // True only when between a callback_->Encoded() call return a positive value @@ -1284,24 +1355,24 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, }; MediaCodecVideoEncoder::~MediaCodecVideoEncoder() { - // We depend on ResetParameters() to ensure no more callbacks to us after we - // are deleted, so assert it here. - CHECK(width_ == 0, "Release() should have been called"); + // Call Release() to ensure no more callbacks to us after we are deleted. + Release(); } MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) - : callback_(NULL), - codec_thread_(new Thread()), - j_media_codec_video_encoder_class_( - jni, - FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")), - j_media_codec_video_encoder_( - jni, - jni->NewObject(*j_media_codec_video_encoder_class_, - GetMethodID(jni, - *j_media_codec_video_encoder_class_, - "<init>", - "()V"))) { + : callback_(NULL), + inited_(false), + codec_thread_(new Thread()), + j_media_codec_video_encoder_class_( + jni, + FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")), + j_media_codec_video_encoder_( + jni, + jni->NewObject(*j_media_codec_video_encoder_class_, + GetMethodID(jni, + *j_media_codec_video_encoder_class_, + "<init>", + "()V"))) { ScopedLocalRefFrame local_ref_frame(jni); // It would be nice to avoid spinning up a new thread per MediaCodec, and // instead re-use e.g. the PeerConnectionFactory's |worker_thread_|, but bug @@ -1311,9 +1382,7 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) // in the bug, we have a problem. For now work around that with a dedicated // thread. codec_thread_->SetName("MediaCodecVideoEncoder", NULL); - CHECK(codec_thread_->Start(), "Failed to start MediaCodecVideoEncoder"); - - ResetParameters(jni); + CHECK(codec_thread_->Start()) << "Failed to start MediaCodecVideoEncoder"; jclass j_output_buffer_info_class = FindClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo"); @@ -1347,7 +1416,7 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) GetFieldID(jni, j_output_buffer_info_class, "isKeyFrame", "Z"); j_info_presentation_timestamp_us_field_ = GetFieldID( jni, j_output_buffer_info_class, "presentationTimestampUs", "J"); - CHECK_EXCEPTION(jni, "MediaCodecVideoEncoder ctor failed"); + CHECK_EXCEPTION(jni) << "MediaCodecVideoEncoder ctor failed"; } int32_t MediaCodecVideoEncoder::InitEncode( @@ -1355,7 +1424,7 @@ int32_t MediaCodecVideoEncoder::InitEncode( int32_t /* number_of_cores */, uint32_t /* max_payload_size */) { // Factory should guard against other codecs being used with us. - CHECK(codec_settings->codecType == kVideoCodecVP8, "Unsupported codec"); + CHECK(codec_settings->codecType == kVideoCodecVP8) << "Unsupported codec"; return codec_thread_->Invoke<int32_t>( Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread, @@ -1407,9 +1476,12 @@ void MediaCodecVideoEncoder::OnMessage(rtc::Message* msg) { // We only ever send one message to |this| directly (not through a Bind()'d // functor), so expect no ID/data. - CHECK(!msg->message_id, "Unexpected message!"); - CHECK(!msg->pdata, "Unexpected message!"); + CHECK(!msg->message_id) << "Unexpected message!"; + CHECK(!msg->pdata) << "Unexpected message!"; CheckOnCodecThread(); + if (!inited_) { + return; + } // It would be nice to recover from a failure here if one happened, but it's // unclear how to signal such a failure to the app, so instead we stay silent @@ -1419,16 +1491,16 @@ void MediaCodecVideoEncoder::OnMessage(rtc::Message* msg) { } void MediaCodecVideoEncoder::CheckOnCodecThread() { - CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread(), - "Running on wrong thread!"); + CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread()) + << "Running on wrong thread!"; } void MediaCodecVideoEncoder::ResetCodec() { ALOGE("ResetCodec"); if (Release() != WEBRTC_VIDEO_CODEC_OK || codec_thread_->Invoke<int32_t>(Bind( - &MediaCodecVideoEncoder::InitEncodeOnCodecThread, this, 0, 0, 0, 0)) - != WEBRTC_VIDEO_CODEC_OK) { + &MediaCodecVideoEncoder::InitEncodeOnCodecThread, this, + width_, height_, 0, 0)) != WEBRTC_VIDEO_CODEC_OK) { // TODO(fischman): wouldn't it be nice if there was a way to gracefully // degrade to a SW encoder at this point? There isn't one AFAICT :( // https://code.google.com/p/webrtc/issues/detail?id=2920 @@ -1440,12 +1512,13 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); - ALOGD("InitEncodeOnCodecThread %d x %d", width, height); - if (width == 0) { - width = width_; - height = height_; + ALOGD("InitEncodeOnCodecThread %d x %d. Bitrate: %d kbps. Fps: %d", + width, height, kbps, fps); + if (kbps == 0) { kbps = last_set_bitrate_kbps_; + } + if (fps == 0) { fps = last_set_fps_; } @@ -1456,9 +1529,19 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( yuv_size_ = width_ * height_ * 3 / 2; frames_received_ = 0; frames_dropped_ = 0; + frames_resolution_update_ = 0; frames_in_queue_ = 0; + current_timestamp_us_ = 0; + start_time_ms_ = GetCurrentTimeMs(); + current_frames_ = 0; + current_bytes_ = 0; + current_encoding_time_ms_ = 0; last_input_timestamp_ms_ = -1; last_output_timestamp_ms_ = -1; + timestamps_.clear(); + render_times_ms_.clear(); + frame_rtc_times_ms_.clear(); + drop_next_input_frame_ = false; // We enforce no extra stride/padding in the format creation step. jobjectArray input_buffers = reinterpret_cast<jobjectArray>( jni->CallObjectMethod(*j_media_codec_video_encoder_, @@ -1467,7 +1550,7 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( height_, kbps, fps)); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (IsNull(jni, input_buffers)) return WEBRTC_VIDEO_CODEC_ERROR; @@ -1487,17 +1570,18 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( return WEBRTC_VIDEO_CODEC_ERROR; } size_t num_input_buffers = jni->GetArrayLength(input_buffers); - CHECK(input_buffers_.empty(), "Unexpected double InitEncode without Release"); + CHECK(input_buffers_.empty()) + << "Unexpected double InitEncode without Release"; input_buffers_.resize(num_input_buffers); for (size_t i = 0; i < num_input_buffers; ++i) { input_buffers_[i] = jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i)); int64 yuv_buffer_capacity = jni->GetDirectBufferCapacity(input_buffers_[i]); - CHECK_EXCEPTION(jni, ""); - CHECK(yuv_buffer_capacity >= yuv_size_, "Insufficient capacity"); + CHECK_EXCEPTION(jni); + CHECK(yuv_buffer_capacity >= yuv_size_) << "Insufficient capacity"; } - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); codec_thread_->PostDelayed(kMediaCodecPollMs, this); return WEBRTC_VIDEO_CODEC_OK; @@ -1510,6 +1594,9 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); + if (!inited_) { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } frames_received_++; if (!DeliverPendingOutputs(jni)) { ResetCodec(); @@ -1517,23 +1604,35 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( } if (drop_next_input_frame_) { + ALOGV("Encoder drop frame - failed callback."); drop_next_input_frame_ = false; return WEBRTC_VIDEO_CODEC_OK; } - CHECK(frame_types->size() == 1, "Unexpected stream count"); - bool key_frame = frame_types->front() != webrtc::kDeltaFrame; + CHECK(frame_types->size() == 1) << "Unexpected stream count"; + if (frame.width() != width_ || frame.height() != height_) { + frames_resolution_update_++; + ALOGD("Unexpected frame resolution change from %d x %d to %d x %d", + width_, height_, frame.width(), frame.height()); + if (frames_resolution_update_ > 3) { + // Reset codec if we received more than 3 frames with new resolution. + width_ = frame.width(); + height_ = frame.height(); + frames_resolution_update_ = 0; + ResetCodec(); + } + return WEBRTC_VIDEO_CODEC_OK; + } + frames_resolution_update_ = 0; - CHECK(frame.width() == width_, "Unexpected resolution change"); - CHECK(frame.height() == height_, "Unexpected resolution change"); + bool key_frame = frame_types->front() != webrtc::kDeltaFrame; // Check if we accumulated too many frames in encoder input buffers - // so the encoder latency exceeds 100ms and drop frame if so. - if (frames_in_queue_ > 0 && last_input_timestamp_ms_ > 0 && - last_output_timestamp_ms_ > 0) { + // or the encoder latency exceeds 70 ms and drop frame if so. + if (frames_in_queue_ > 0 && last_input_timestamp_ms_ >= 0) { int encoder_latency_ms = last_input_timestamp_ms_ - last_output_timestamp_ms_; - if (encoder_latency_ms > 100) { + if (frames_in_queue_ > 2 || encoder_latency_ms > 70) { ALOGV("Drop frame - encoder is behind by %d ms. Q size: %d", encoder_latency_ms, frames_in_queue_); frames_dropped_++; @@ -1543,10 +1642,10 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_, j_dequeue_input_buffer_method_); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (j_input_buffer_index == -1) { // Video codec falls behind - no input buffer available. - ALOGV("Drop frame - no input buffers available"); + ALOGV("Encoder drop frame - no input buffers available"); frames_dropped_++; return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887. } @@ -1556,31 +1655,38 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( } ALOGV("Encode frame # %d. Buffer # %d. TS: %lld.", - frames_received_, j_input_buffer_index, frame.render_time_ms()); + frames_received_, j_input_buffer_index, current_timestamp_us_ / 1000); jobject j_input_buffer = input_buffers_[j_input_buffer_index]; uint8* yuv_buffer = reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer)); - CHECK_EXCEPTION(jni, ""); - CHECK(yuv_buffer, "Indirect buffer??"); + CHECK_EXCEPTION(jni); + CHECK(yuv_buffer) << "Indirect buffer??"; CHECK(!libyuv::ConvertFromI420( frame.buffer(webrtc::kYPlane), frame.stride(webrtc::kYPlane), frame.buffer(webrtc::kUPlane), frame.stride(webrtc::kUPlane), frame.buffer(webrtc::kVPlane), frame.stride(webrtc::kVPlane), yuv_buffer, width_, width_, height_, - encoder_fourcc_), - "ConvertFromI420 failed"); - jlong timestamp_us = frame.render_time_ms() * 1000; - last_input_timestamp_ms_ = frame.render_time_ms(); + encoder_fourcc_)) + << "ConvertFromI420 failed"; + last_input_timestamp_ms_ = current_timestamp_us_ / 1000; frames_in_queue_++; + + // Save input image timestamps for later output + timestamps_.push_back(frame.timestamp()); + render_times_ms_.push_back(frame.render_time_ms()); + frame_rtc_times_ms_.push_back(GetCurrentTimeMs()); + bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_encode_method_, key_frame, j_input_buffer_index, yuv_size_, - timestamp_us); - CHECK_EXCEPTION(jni, ""); + current_timestamp_us_); + CHECK_EXCEPTION(jni); + current_timestamp_us_ += 1000000 / last_set_fps_; + if (!encode_status || !DeliverPendingOutputs(jni)) { ResetCodec(); return WEBRTC_VIDEO_CODEC_ERROR; @@ -1599,8 +1705,9 @@ int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread( } int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { - if (!inited_) + if (!inited_) { return WEBRTC_VIDEO_CODEC_OK; + } CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); ALOGD("EncoderRelease: Frames received: %d. Frames dropped: %d.", @@ -1610,8 +1717,9 @@ int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { jni->DeleteGlobalRef(input_buffers_[i]); input_buffers_.clear(); jni->CallVoidMethod(*j_media_codec_video_encoder_, j_release_method_); - ResetParameters(jni); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); + rtc::MessageQueueManager::Clear(this); + inited_ = false; return WEBRTC_VIDEO_CODEC_OK; } @@ -1624,13 +1732,17 @@ int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, } JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); - last_set_bitrate_kbps_ = new_bit_rate; - last_set_fps_ = frame_rate; + if (new_bit_rate > 0) { + last_set_bitrate_kbps_ = new_bit_rate; + } + if (frame_rate > 0) { + last_set_fps_ = frame_rate; + } bool ret = jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_set_rates_method_, - new_bit_rate, - frame_rate); - CHECK_EXCEPTION(jni, ""); + last_set_bitrate_kbps_, + last_set_fps_); + CHECK_EXCEPTION(jni); if (!ret) { ResetCodec(); return WEBRTC_VIDEO_CODEC_ERROR; @@ -1638,17 +1750,6 @@ int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, return WEBRTC_VIDEO_CODEC_OK; } -void MediaCodecVideoEncoder::ResetParameters(JNIEnv* jni) { - rtc::MessageQueueManager::Clear(this); - width_ = 0; - height_ = 0; - yuv_size_ = 0; - drop_next_input_frame_ = false; - inited_ = false; - CHECK(input_buffers_.empty(), - "ResetParameters called while holding input_buffers_!"); -} - int MediaCodecVideoEncoder::GetOutputBufferInfoIndex( JNIEnv* jni, jobject j_output_buffer_info) { @@ -1678,9 +1779,10 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { while (true) { jobject j_output_buffer_info = jni->CallObjectMethod( *j_media_codec_video_encoder_, j_dequeue_output_buffer_method_); - CHECK_EXCEPTION(jni, ""); - if (IsNull(jni, j_output_buffer_info)) + CHECK_EXCEPTION(jni); + if (IsNull(jni, j_output_buffer_info)) { break; + } int output_buffer_index = GetOutputBufferInfoIndex(jni, j_output_buffer_info); @@ -1689,31 +1791,62 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { return false; } - jlong capture_time_ms = + // Get frame timestamps from a queue. + last_output_timestamp_ms_ = GetOutputBufferInfoPresentationTimestampUs(jni, j_output_buffer_info) / 1000; - last_output_timestamp_ms_ = capture_time_ms; + int32_t timestamp = timestamps_.front(); + timestamps_.erase(timestamps_.begin()); + int64_t render_time_ms = render_times_ms_.front(); + render_times_ms_.erase(render_times_ms_.begin()); + int64_t frame_encoding_time_ms = GetCurrentTimeMs() - + frame_rtc_times_ms_.front(); + frame_rtc_times_ms_.erase(frame_rtc_times_ms_.begin()); frames_in_queue_--; - ALOGV("Encoder got output buffer # %d. TS: %lld. Latency: %lld", - output_buffer_index, last_output_timestamp_ms_, - last_input_timestamp_ms_ - last_output_timestamp_ms_); + // Extract payload and key frame flag. int32_t callback_status = 0; + jobject j_output_buffer = + GetOutputBufferInfoBuffer(jni, j_output_buffer_info); + bool key_frame = GetOutputBufferInfoIsKeyFrame(jni, j_output_buffer_info); + size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer); + uint8* payload = reinterpret_cast<uint8_t*>( + jni->GetDirectBufferAddress(j_output_buffer)); + CHECK_EXCEPTION(jni); + + ALOGV("Encoder got output buffer # %d. Size: %d. TS: %lld. Latency: %lld." + " EncTime: %lld", + output_buffer_index, payload_size, last_output_timestamp_ms_, + last_input_timestamp_ms_ - last_output_timestamp_ms_, + frame_encoding_time_ms); + + // Calculate and print encoding statistics - every 3 seconds. + current_frames_++; + current_bytes_ += payload_size; + current_encoding_time_ms_ += frame_encoding_time_ms; + int statistic_time_ms = GetCurrentTimeMs() - start_time_ms_; + if (statistic_time_ms >= kMediaCodecStatisticsIntervalMs && + current_frames_ > 0) { + ALOGD("Encoder bitrate: %d, target: %d kbps, fps: %d," + " encTime: %d for last %d ms", + current_bytes_ * 8 / statistic_time_ms, + last_set_bitrate_kbps_, + (current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms, + current_encoding_time_ms_ / current_frames_, statistic_time_ms); + start_time_ms_ = GetCurrentTimeMs(); + current_frames_ = 0; + current_bytes_= 0; + current_encoding_time_ms_ = 0; + } + + // Callback - return encoded frame. if (callback_) { - jobject j_output_buffer = - GetOutputBufferInfoBuffer(jni, j_output_buffer_info); - bool key_frame = GetOutputBufferInfoIsKeyFrame(jni, j_output_buffer_info); - size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer); - uint8* payload = reinterpret_cast<uint8_t*>( - jni->GetDirectBufferAddress(j_output_buffer)); - CHECK_EXCEPTION(jni, ""); scoped_ptr<webrtc::EncodedImage> image( new webrtc::EncodedImage(payload, payload_size, payload_size)); image->_encodedWidth = width_; image->_encodedHeight = height_; - // Convert capture time to 90 kHz RTP timestamp. - image->_timeStamp = static_cast<uint32_t>(90 * capture_time_ms); - image->capture_time_ms_ = capture_time_ms; + image->_timeStamp = timestamp; + image->capture_time_ms_ = render_time_ms; image->_frameType = (key_frame ? webrtc::kKeyFrame : webrtc::kDeltaFrame); image->_completeFrame = true; @@ -1736,19 +1869,21 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { callback_status = callback_->Encoded(*image, &info, &header); } + // Return output buffer back to the encoder. bool success = jni->CallBooleanMethod(*j_media_codec_video_encoder_, j_release_output_buffer_method_, output_buffer_index); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (!success) { ResetCodec(); return false; } - if (callback_status > 0) + if (callback_status > 0) { drop_next_input_frame_ = true; // Theoretically could handle callback_status<0 here, but unclear what that // would mean for us. + } } return true; @@ -1782,7 +1917,7 @@ MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() { bool is_platform_supported = jni->CallStaticBooleanMethod( j_encoder_class, GetStaticMethodID(jni, j_encoder_class, "isPlatformSupported", "()Z")); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (!is_platform_supported) return; @@ -1823,6 +1958,8 @@ class MediaCodecVideoDecoder : public webrtc::VideoDecoder, explicit MediaCodecVideoDecoder(JNIEnv* jni); virtual ~MediaCodecVideoDecoder(); + static int SetAndroidObjects(JNIEnv* jni, jobject render_egl_context); + virtual int32_t InitDecode(const VideoCodec* codecSettings, int32_t numberOfCores) OVERRIDE; @@ -1848,13 +1985,29 @@ class MediaCodecVideoDecoder : public webrtc::VideoDecoder, int32_t InitDecodeOnCodecThread(); int32_t ReleaseOnCodecThread(); int32_t DecodeOnCodecThread(const EncodedImage& inputImage); + // Deliver any outputs pending in the MediaCodec to our |callback_| and return + // true on success. + bool DeliverPendingOutputs(JNIEnv* jni, int dequeue_timeout_us); + bool key_frame_required_; bool inited_; + bool use_surface_; VideoCodec codec_; I420VideoFrame decoded_image_; + NativeHandleImpl native_handle_; DecodedImageCallback* callback_; - int frames_received_; // Number of frames received by decoder. + int frames_received_; // Number of frames received by decoder. + int frames_decoded_; // Number of frames decoded by decoder + int64_t start_time_ms_; // Start time for statistics. + int current_frames_; // Number of frames in the current statistics interval. + int current_bytes_; // Encoded bytes in the current statistics interval. + int current_decoding_time_ms_; // Overall decoding time in the current second + uint32_t max_pending_frames_; // Maximum number of pending input frames + std::vector<int32_t> timestamps_; + std::vector<int64_t> ntp_times_ms_; + std::vector<int64_t> frame_rtc_times_ms_; // Time when video frame is sent to + // decoder input. // State that is constant for the lifetime of this object once the ctor // returns. @@ -1867,6 +2020,7 @@ class MediaCodecVideoDecoder : public webrtc::VideoDecoder, jmethodID j_queue_input_buffer_method_; jmethodID j_dequeue_output_buffer_method_; jmethodID j_release_output_buffer_method_; + // MediaCodecVideoDecoder fields. jfieldID j_input_buffers_field_; jfieldID j_output_buffers_field_; jfieldID j_color_format_field_; @@ -1874,32 +2028,59 @@ class MediaCodecVideoDecoder : public webrtc::VideoDecoder, jfieldID j_height_field_; jfieldID j_stride_field_; jfieldID j_slice_height_field_; + jfieldID j_surface_texture_field_; + jfieldID j_textureID_field_; + // MediaCodecVideoDecoder.DecoderOutputBufferInfo fields. + jfieldID j_info_index_field_; + jfieldID j_info_offset_field_; + jfieldID j_info_size_field_; + jfieldID j_info_presentation_timestamp_us_field_; // Global references; must be deleted in Release(). std::vector<jobject> input_buffers_; + jobject surface_texture_; + + // Render EGL context. + static jobject render_egl_context_; }; -MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni) : - key_frame_required_(true), - inited_(false), - codec_thread_(new Thread()), - j_media_codec_video_decoder_class_( - jni, - FindClass(jni, "org/webrtc/MediaCodecVideoDecoder")), - j_media_codec_video_decoder_( - jni, - jni->NewObject(*j_media_codec_video_decoder_class_, - GetMethodID(jni, - *j_media_codec_video_decoder_class_, - "<init>", - "()V"))) { +jobject MediaCodecVideoDecoder::render_egl_context_ = NULL; + +int MediaCodecVideoDecoder::SetAndroidObjects(JNIEnv* jni, + jobject render_egl_context) { + if (render_egl_context_) { + jni->DeleteGlobalRef(render_egl_context_); + } + if (IsNull(jni, render_egl_context)) { + render_egl_context_ = NULL; + } else { + render_egl_context_ = jni->NewGlobalRef(render_egl_context); + } + ALOGD("VideoDecoder EGL context set."); + return 0; +} + +MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni) + : key_frame_required_(true), + inited_(false), + codec_thread_(new Thread()), + j_media_codec_video_decoder_class_( + jni, + FindClass(jni, "org/webrtc/MediaCodecVideoDecoder")), + j_media_codec_video_decoder_( + jni, + jni->NewObject(*j_media_codec_video_decoder_class_, + GetMethodID(jni, + *j_media_codec_video_decoder_class_, + "<init>", + "()V"))) { ScopedLocalRefFrame local_ref_frame(jni); codec_thread_->SetName("MediaCodecVideoDecoder", NULL); - CHECK(codec_thread_->Start(), "Failed to start MediaCodecVideoDecoder"); + CHECK(codec_thread_->Start()) << "Failed to start MediaCodecVideoDecoder"; - j_init_decode_method_ = GetMethodID(jni, - *j_media_codec_video_decoder_class_, - "initDecode", "(II)Z"); + j_init_decode_method_ = GetMethodID( + jni, *j_media_codec_video_decoder_class_, "initDecode", + "(IIZLandroid/opengl/EGLContext;)Z"); j_release_method_ = GetMethodID(jni, *j_media_codec_video_decoder_class_, "release", "()V"); j_dequeue_input_buffer_method_ = GetMethodID( @@ -1907,9 +2088,10 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni) : j_queue_input_buffer_method_ = GetMethodID( jni, *j_media_codec_video_decoder_class_, "queueInputBuffer", "(IIJ)Z"); j_dequeue_output_buffer_method_ = GetMethodID( - jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer", "()I"); + jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer", + "(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;"); j_release_output_buffer_method_ = GetMethodID( - jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(I)Z"); + jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(IZ)Z"); j_input_buffers_field_ = GetFieldID( jni, *j_media_codec_video_decoder_class_, @@ -1927,12 +2109,32 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni) : jni, *j_media_codec_video_decoder_class_, "stride", "I"); j_slice_height_field_ = GetFieldID( jni, *j_media_codec_video_decoder_class_, "sliceHeight", "I"); + j_textureID_field_ = GetFieldID( + jni, *j_media_codec_video_decoder_class_, "textureID", "I"); + j_surface_texture_field_ = GetFieldID( + jni, *j_media_codec_video_decoder_class_, "surfaceTexture", + "Landroid/graphics/SurfaceTexture;"); + + jclass j_decoder_output_buffer_info_class = FindClass(jni, + "org/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo"); + j_info_index_field_ = GetFieldID( + jni, j_decoder_output_buffer_info_class, "index", "I"); + j_info_offset_field_ = GetFieldID( + jni, j_decoder_output_buffer_info_class, "offset", "I"); + j_info_size_field_ = GetFieldID( + jni, j_decoder_output_buffer_info_class, "size", "I"); + j_info_presentation_timestamp_us_field_ = GetFieldID( + jni, j_decoder_output_buffer_info_class, "presentationTimestampUs", "J"); - CHECK_EXCEPTION(jni, "MediaCodecVideoDecoder ctor failed"); + CHECK_EXCEPTION(jni) << "MediaCodecVideoDecoder ctor failed"; + use_surface_ = true; + if (render_egl_context_ == NULL) + use_surface_ = false; memset(&codec_, 0, sizeof(codec_)); } MediaCodecVideoDecoder::~MediaCodecVideoDecoder() { + // Call Release() to ensure no more callbacks to us after we are deleted. Release(); } @@ -1954,6 +2156,7 @@ int32_t MediaCodecVideoDecoder::InitDecode(const VideoCodec* inst, // Always start with a complete key frame. key_frame_required_ = true; frames_received_ = 0; + frames_decoded_ = 0; // Call Java init. return codec_thread_->Invoke<int32_t>( @@ -1964,28 +2167,50 @@ int32_t MediaCodecVideoDecoder::InitDecodeOnCodecThread() { CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); - ALOGD("InitDecodeOnCodecThread: %d x %d. FPS: %d", + ALOGD("InitDecodeOnCodecThread: %d x %d. fps: %d", codec_.width, codec_.height, codec_.maxFramerate); bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_, j_init_decode_method_, codec_.width, - codec_.height); - CHECK_EXCEPTION(jni, ""); - if (!success) + codec_.height, + use_surface_, + render_egl_context_); + CHECK_EXCEPTION(jni); + if (!success) { return WEBRTC_VIDEO_CODEC_ERROR; + } inited_ = true; + max_pending_frames_ = 0; + if (use_surface_) { + max_pending_frames_ = 1; + } + start_time_ms_ = GetCurrentTimeMs(); + current_frames_ = 0; + current_bytes_ = 0; + current_decoding_time_ms_ = 0; + timestamps_.clear(); + ntp_times_ms_.clear(); + frame_rtc_times_ms_.clear(); + jobjectArray input_buffers = (jobjectArray)GetObjectField( jni, *j_media_codec_video_decoder_, j_input_buffers_field_); size_t num_input_buffers = jni->GetArrayLength(input_buffers); - input_buffers_.resize(num_input_buffers); for (size_t i = 0; i < num_input_buffers; ++i) { input_buffers_[i] = jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i)); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); + } + + if (use_surface_) { + jobject surface_texture = GetObjectField( + jni, *j_media_codec_video_decoder_, j_surface_texture_field_); + surface_texture_ = jni->NewGlobalRef(surface_texture); } + codec_thread_->PostDelayed(kMediaCodecPollMs, this); + return WEBRTC_VIDEO_CODEC_OK; } @@ -1995,25 +2220,40 @@ int32_t MediaCodecVideoDecoder::Release() { } int32_t MediaCodecVideoDecoder::ReleaseOnCodecThread() { - if (!inited_) + if (!inited_) { return WEBRTC_VIDEO_CODEC_OK; + } CheckOnCodecThread(); JNIEnv* jni = AttachCurrentThreadIfNeeded(); ALOGD("DecoderRelease: Frames received: %d.", frames_received_); ScopedLocalRefFrame local_ref_frame(jni); - for (size_t i = 0; i < input_buffers_.size(); ++i) + for (size_t i = 0; i < input_buffers_.size(); i++) { jni->DeleteGlobalRef(input_buffers_[i]); + } input_buffers_.clear(); + if (use_surface_) { + // Before deleting texture object make sure it is no longer referenced + // by any TextureVideoFrame. + int32_t waitTimeoutUs = 3000000; // 3 second wait + while (waitTimeoutUs > 0 && native_handle_.ref_count() > 0) { + ALOGD("Current Texture RefCnt: %d", native_handle_.ref_count()); + usleep(30000); + waitTimeoutUs -= 30000; + } + ALOGD("TextureRefCnt: %d", native_handle_.ref_count()); + jni->DeleteGlobalRef(surface_texture_); + } jni->CallVoidMethod(*j_media_codec_video_decoder_, j_release_method_); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); + rtc::MessageQueueManager::Clear(this); inited_ = false; return WEBRTC_VIDEO_CODEC_OK; } void MediaCodecVideoDecoder::CheckOnCodecThread() { - CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread(), - "Running on wrong thread!"); + CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread()) + << "Running on wrong thread!"; } int32_t MediaCodecVideoDecoder::Decode( @@ -2066,10 +2306,25 @@ int32_t MediaCodecVideoDecoder::DecodeOnCodecThread( JNIEnv* jni = AttachCurrentThreadIfNeeded(); ScopedLocalRefFrame local_ref_frame(jni); + // Try to drain the decoder and wait until output is not too + // much behind the input. + if (frames_received_ > frames_decoded_ + max_pending_frames_) { + ALOGV("Wait for output..."); + if (!DeliverPendingOutputs(jni, kMediaCodecTimeoutMs * 1000)) { + Reset(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + if (frames_received_ > frames_decoded_ + max_pending_frames_) { + ALOGE("Output buffer dequeue timeout"); + Reset(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + } + // Get input buffer. int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_decoder_, j_dequeue_input_buffer_method_); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (j_input_buffer_index < 0) { ALOGE("dequeueInputBuffer error"); Reset(); @@ -2080,19 +2335,26 @@ int32_t MediaCodecVideoDecoder::DecodeOnCodecThread( jobject j_input_buffer = input_buffers_[j_input_buffer_index]; uint8* buffer = reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer)); - CHECK(buffer, "Indirect buffer??"); + CHECK(buffer) << "Indirect buffer??"; int64 buffer_capacity = jni->GetDirectBufferCapacity(j_input_buffer); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (buffer_capacity < inputImage._length) { ALOGE("Input frame size %d is bigger than buffer size %d.", inputImage._length, buffer_capacity); Reset(); return WEBRTC_VIDEO_CODEC_ERROR; } - ALOGV("Decode frame # %d. Buffer # %d. Size: %d", + ALOGV("Decoder frame in # %d. Buffer # %d. Size: %d", frames_received_, j_input_buffer_index, inputImage._length); memcpy(buffer, inputImage._buffer, inputImage._length); + // Save input image timestamps for later output. + frames_received_++; + current_bytes_ += inputImage._length; + timestamps_.push_back(inputImage._timeStamp); + ntp_times_ms_.push_back(inputImage.ntp_time_ms_); + frame_rtc_times_ms_.push_back(GetCurrentTimeMs()); + // Feed input to decoder. jlong timestamp_us = (frames_received_ * 1000000) / codec_.maxFramerate; bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_, @@ -2100,33 +2362,55 @@ int32_t MediaCodecVideoDecoder::DecodeOnCodecThread( j_input_buffer_index, inputImage._length, timestamp_us); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); if (!success) { ALOGE("queueInputBuffer error"); Reset(); return WEBRTC_VIDEO_CODEC_ERROR; } - // Get output index. - int j_output_buffer_index = - jni->CallIntMethod(*j_media_codec_video_decoder_, - j_dequeue_output_buffer_method_); - CHECK_EXCEPTION(jni, ""); - if (j_output_buffer_index < 0) { - ALOGE("dequeueOutputBuffer error"); + // Try to drain the decoder + if (!DeliverPendingOutputs(jni, 0)) { + ALOGE("DeliverPendingOutputs error"); Reset(); return WEBRTC_VIDEO_CODEC_ERROR; } - // Extract data from Java ByteBuffer. - jobjectArray output_buffers = reinterpret_cast<jobjectArray>(GetObjectField( - jni, *j_media_codec_video_decoder_, j_output_buffers_field_)); - jobject output_buffer = - jni->GetObjectArrayElement(output_buffers, j_output_buffer_index); - buffer_capacity = jni->GetDirectBufferCapacity(output_buffer); - uint8_t* payload = - reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(output_buffer)); - CHECK_EXCEPTION(jni, ""); + return WEBRTC_VIDEO_CODEC_OK; +} + +bool MediaCodecVideoDecoder::DeliverPendingOutputs( + JNIEnv* jni, int dequeue_timeout_us) { + if (frames_received_ <= frames_decoded_) { + // No need to query for output buffers - decoder is drained. + return true; + } + // Get decoder output. + jobject j_decoder_output_buffer_info = jni->CallObjectMethod( + *j_media_codec_video_decoder_, + j_dequeue_output_buffer_method_, + dequeue_timeout_us); + + CHECK_EXCEPTION(jni); + if (IsNull(jni, j_decoder_output_buffer_info)) { + return true; + } + + // Extract output buffer info from Java DecoderOutputBufferInfo. + int output_buffer_index = + GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_); + if (output_buffer_index < 0) { + ALOGE("dequeueOutputBuffer error : %d", output_buffer_index); + Reset(); + return false; + } + int output_buffer_offset = + GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_); + int output_buffer_size = + GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_); + CHECK_EXCEPTION(jni); + + // Get decoded video frame properties. int color_format = GetIntField(jni, *j_media_codec_video_decoder_, j_color_format_field_); int width = GetIntField(jni, *j_media_codec_video_decoder_, j_width_field_); @@ -2134,52 +2418,112 @@ int32_t MediaCodecVideoDecoder::DecodeOnCodecThread( int stride = GetIntField(jni, *j_media_codec_video_decoder_, j_stride_field_); int slice_height = GetIntField(jni, *j_media_codec_video_decoder_, j_slice_height_field_); - if (buffer_capacity < width * height * 3 / 2) { - ALOGE("Insufficient output buffer capacity: %d", buffer_capacity); - Reset(); - return WEBRTC_VIDEO_CODEC_ERROR; + int texture_id = GetIntField(jni, *j_media_codec_video_decoder_, + j_textureID_field_); + + // Extract data from Java ByteBuffer and create output yuv420 frame - + // for non surface decoding only. + if (!use_surface_) { + if (output_buffer_size < width * height * 3 / 2) { + ALOGE("Insufficient output buffer size: %d", output_buffer_size); + Reset(); + return false; + } + jobjectArray output_buffers = reinterpret_cast<jobjectArray>(GetObjectField( + jni, *j_media_codec_video_decoder_, j_output_buffers_field_)); + jobject output_buffer = + jni->GetObjectArrayElement(output_buffers, output_buffer_index); + uint8_t* payload = reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress( + output_buffer)); + CHECK_EXCEPTION(jni); + payload += output_buffer_offset; + + // Create yuv420 frame. + if (color_format == COLOR_FormatYUV420Planar) { + decoded_image_.CreateFrame( + stride * slice_height, payload, + (stride * slice_height) / 4, payload + (stride * slice_height), + (stride * slice_height) / 4, payload + (5 * stride * slice_height / 4), + width, height, + stride, stride / 2, stride / 2); + } else { + // All other supported formats are nv12. + decoded_image_.CreateEmptyFrame(width, height, width, + width / 2, width / 2); + libyuv::NV12ToI420( + payload, stride, + payload + stride * slice_height, stride, + decoded_image_.buffer(webrtc::kYPlane), + decoded_image_.stride(webrtc::kYPlane), + decoded_image_.buffer(webrtc::kUPlane), + decoded_image_.stride(webrtc::kUPlane), + decoded_image_.buffer(webrtc::kVPlane), + decoded_image_.stride(webrtc::kVPlane), + width, height); + } } - ALOGV("Decoder got output buffer %d x %d. %d x %d. Color: 0x%x. Size: %d", - width, height, stride, slice_height, color_format, buffer_capacity); - if (color_format == COLOR_FormatYUV420Planar) { - decoded_image_.CreateFrame( - stride * slice_height, payload, - (stride * slice_height) / 4, payload + (stride * slice_height), - (stride * slice_height) / 4, payload + (5 * stride * slice_height / 4), - width, height, - stride, stride / 2, stride / 2); - } else { - // All other supported formats are nv12. - decoded_image_.CreateEmptyFrame(width, height, width, width / 2, width / 2); - libyuv::NV12ToI420( - payload, stride, - payload + stride * slice_height, stride, - decoded_image_.buffer(webrtc::kYPlane), - decoded_image_.stride(webrtc::kYPlane), - decoded_image_.buffer(webrtc::kUPlane), - decoded_image_.stride(webrtc::kUPlane), - decoded_image_.buffer(webrtc::kVPlane), - decoded_image_.stride(webrtc::kVPlane), - width, height); - } + // Get frame timestamps from a queue. + int32_t timestamp = timestamps_.front(); + timestamps_.erase(timestamps_.begin()); + int64_t ntp_time_ms = ntp_times_ms_.front(); + ntp_times_ms_.erase(ntp_times_ms_.begin()); + int64_t frame_decoding_time_ms = GetCurrentTimeMs() - + frame_rtc_times_ms_.front(); + frame_rtc_times_ms_.erase(frame_rtc_times_ms_.begin()); + + ALOGV("Decoder frame out # %d. %d x %d. %d x %d. Color: 0x%x. Size: %d." + " DecTime: %lld", frames_decoded_, width, height, stride, slice_height, + color_format, output_buffer_size, frame_decoding_time_ms); // Return output buffer back to codec. - success = jni->CallBooleanMethod(*j_media_codec_video_decoder_, - j_release_output_buffer_method_, - j_output_buffer_index); - CHECK_EXCEPTION(jni, ""); + bool success = jni->CallBooleanMethod( + *j_media_codec_video_decoder_, + j_release_output_buffer_method_, + output_buffer_index, + use_surface_); + CHECK_EXCEPTION(jni); if (!success) { ALOGE("releaseOutputBuffer error"); Reset(); - return WEBRTC_VIDEO_CODEC_ERROR; + return false; + } + + // Calculate and print decoding statistics - every 3 seconds. + frames_decoded_++; + current_frames_++; + current_decoding_time_ms_ += frame_decoding_time_ms; + int statistic_time_ms = GetCurrentTimeMs() - start_time_ms_; + if (statistic_time_ms >= kMediaCodecStatisticsIntervalMs && + current_frames_ > 0) { + ALOGD("Decoder bitrate: %d kbps, fps: %d, decTime: %d for last %d ms", + current_bytes_ * 8 / statistic_time_ms, + (current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms, + current_decoding_time_ms_ / current_frames_, statistic_time_ms); + start_time_ms_ = GetCurrentTimeMs(); + current_frames_ = 0; + current_bytes_= 0; + current_decoding_time_ms_ = 0; + } + + // Callback - output decoded frame. + int32_t callback_status = WEBRTC_VIDEO_CODEC_OK; + if (use_surface_) { + native_handle_.SetTextureObject(surface_texture_, texture_id); + TextureVideoFrame texture_image( + &native_handle_, width, height, timestamp, 0); + texture_image.set_ntp_time_ms(ntp_time_ms); + callback_status = callback_->Decoded(texture_image); + } else { + decoded_image_.set_timestamp(timestamp); + decoded_image_.set_ntp_time_ms(ntp_time_ms); + callback_status = callback_->Decoded(decoded_image_); + } + if (callback_status > 0) { + ALOGE("callback error"); } - // Callback. - decoded_image_.set_timestamp(inputImage._timeStamp); - decoded_image_.set_ntp_time_ms(inputImage.ntp_time_ms_); - frames_received_++; - return callback_->Decoded(decoded_image_); + return true; } int32_t MediaCodecVideoDecoder::RegisterDecodeCompleteCallback( @@ -2197,6 +2541,19 @@ int32_t MediaCodecVideoDecoder::Reset() { } void MediaCodecVideoDecoder::OnMessage(rtc::Message* msg) { + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + if (!inited_) { + return; + } + // We only ever send one message to |this| directly (not through a Bind()'d + // functor), so expect no ID/data. + CHECK(!msg->message_id) << "Unexpected message!"; + CHECK(!msg->pdata) << "Unexpected message!"; + CheckOnCodecThread(); + + DeliverPendingOutputs(jni, 0); + codec_thread_->PostDelayed(kMediaCodecPollMs, this); } class MediaCodecVideoDecoderFactory @@ -2221,7 +2578,7 @@ MediaCodecVideoDecoderFactory::MediaCodecVideoDecoderFactory() { is_platform_supported_ = jni->CallStaticBooleanMethod( j_decoder_class, GetStaticMethodID(jni, j_decoder_class, "isPlatformSupported", "()Z")); - CHECK_EXCEPTION(jni, ""); + CHECK_EXCEPTION(jni); } MediaCodecVideoDecoderFactory::~MediaCodecVideoDecoderFactory() {} @@ -2240,7 +2597,7 @@ void MediaCodecVideoDecoderFactory::DestroyVideoDecoder( delete decoder; } -#endif // ANDROID +#endif // #if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) } // anonymous namespace @@ -2250,13 +2607,13 @@ void MediaCodecVideoDecoderFactory::DestroyVideoDecoder( Java_org_webrtc_##name extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { - CHECK(!g_jvm, "JNI_OnLoad called more than once!"); + CHECK(!g_jvm) << "JNI_OnLoad called more than once!"; g_jvm = jvm; - CHECK(g_jvm, "JNI_OnLoad handed NULL?"); + CHECK(g_jvm) << "JNI_OnLoad handed NULL?"; - CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey), "pthread_once"); + CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey)) << "pthread_once"; - CHECK(rtc::InitializeSSL(), "Failed to InitializeSSL()"); + CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()"; JNIEnv* jni; if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK) @@ -2270,7 +2627,7 @@ extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM *jvm, void *reserved) { g_class_reference_holder->FreeReferences(AttachCurrentThreadIfNeeded()); delete g_class_reference_holder; g_class_reference_holder = NULL; - CHECK(rtc::CleanupSSL(), "Failed to CleanupSSL()"); + CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()"; g_jvm = NULL; } @@ -2306,8 +2663,8 @@ JOW(jobject, DataChannel_state)(JNIEnv* jni, jobject j_dc) { JOW(jlong, DataChannel_bufferedAmount)(JNIEnv* jni, jobject j_dc) { uint64 buffered_amount = ExtractNativeDC(jni, j_dc)->buffered_amount(); - CHECK(buffered_amount <= std::numeric_limits<int64>::max(), - "buffered_amount overflowed jlong!"); + CHECK_LE(buffered_amount, std::numeric_limits<int64>::max()) + << "buffered_amount overflowed jlong!"; return static_cast<jlong>(buffered_amount); } @@ -2335,12 +2692,12 @@ JOW(void, Logging_nativeEnableTracing)( std::string path = JavaToStdString(jni, j_path); if (nativeLevels != webrtc::kTraceNone) { webrtc::Trace::set_level_filter(nativeLevels); -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) if (path != "logcat:") { #endif - CHECK(webrtc::Trace::SetTraceFile(path.c_str(), false) == 0, - "SetTraceFile failed"); -#ifdef ANDROID + CHECK_EQ(0, webrtc::Trace::SetTraceFile(path.c_str(), false)) + << "SetTraceFile failed"; +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) } else { // Intentionally leak this to avoid needing to reason about its lifecycle. // It keeps no state and functions only as a dispatch point. @@ -2368,10 +2725,14 @@ JOW(void, VideoCapturer_free)(JNIEnv*, jclass, jlong j_p) { delete reinterpret_cast<cricket::VideoCapturer*>(j_p); } -JOW(void, VideoRenderer_free)(JNIEnv*, jclass, jlong j_p) { +JOW(void, VideoRenderer_freeGuiVideoRenderer)(JNIEnv*, jclass, jlong j_p) { delete reinterpret_cast<VideoRendererWrapper*>(j_p); } +JOW(void, VideoRenderer_freeWrappedVideoRenderer)(JNIEnv*, jclass, jlong j_p) { + delete reinterpret_cast<JavaVideoRendererWrapper*>(j_p); +} + JOW(void, MediaStreamTrack_free)(JNIEnv*, jclass, jlong j_p) { CHECK_RELEASE(reinterpret_cast<MediaStreamTrackInterface*>(j_p)); } @@ -2414,19 +2775,28 @@ JOW(jlong, PeerConnectionFactory_nativeCreateObserver)( return (jlong)new PCOJava(jni, j_observer); } -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)( JNIEnv* jni, jclass, jobject context, - jboolean initialize_audio, jboolean initialize_video) { - CHECK(g_jvm, "JNI_OnLoad failed to run?"); + jboolean initialize_audio, jboolean initialize_video, + jobject render_egl_context) { + CHECK(g_jvm) << "JNI_OnLoad failed to run?"; bool failure = false; + if (!factory_static_initialized) { + if (initialize_video) { + failure |= webrtc::SetCaptureAndroidVM(g_jvm, context); + failure |= webrtc::SetRenderAndroidVM(g_jvm); + } + if (initialize_audio) + failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context); + factory_static_initialized = true; + } if (initialize_video) - failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context); - if (initialize_audio) - failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context); + failure |= MediaCodecVideoDecoder::SetAndroidObjects(jni, + render_egl_context); return !failure; } -#endif // ANDROID +#endif // defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) // Helper struct for working around the fact that CreatePeerConnectionFactory() // comes in two flavors: either entirely automagical (constructing its own @@ -2466,11 +2836,11 @@ JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)( worker_thread->SetName("worker_thread", NULL); Thread* signaling_thread = new Thread(); signaling_thread->SetName("signaling_thread", NULL); - CHECK(worker_thread->Start() && signaling_thread->Start(), - "Failed to start threads"); + CHECK(worker_thread->Start() && signaling_thread->Start()) + << "Failed to start threads"; scoped_ptr<cricket::WebRtcVideoEncoderFactory> encoder_factory; scoped_ptr<cricket::WebRtcVideoDecoderFactory> decoder_factory; -#ifdef ANDROID +#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) encoder_factory.reset(new MediaCodecVideoEncoderFactory()); decoder_factory.reset(new MediaCodecVideoDecoderFactory()); #endif @@ -2558,15 +2928,15 @@ static void JavaIceServersToJsepIceServers( jmethodID iterator_id = GetMethodID( jni, list_class, "iterator", "()Ljava/util/Iterator;"); jobject iterator = jni->CallObjectMethod(j_ice_servers, iterator_id); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jmethodID iterator_has_next = GetMethodID( jni, GetObjectClass(jni, iterator), "hasNext", "()Z"); jmethodID iterator_next = GetMethodID( jni, GetObjectClass(jni, iterator), "next", "()Ljava/lang/Object;"); while (jni->CallBooleanMethod(iterator, iterator_has_next)) { - CHECK_EXCEPTION(jni, "error during CallBooleanMethod"); + CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; jobject j_ice_server = jni->CallObjectMethod(iterator, iterator_next); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; jclass j_ice_server_class = GetObjectClass(jni, j_ice_server); jfieldID j_ice_server_uri_id = GetFieldID(jni, j_ice_server_class, "uri", "Ljava/lang/String;"); @@ -2586,7 +2956,7 @@ static void JavaIceServersToJsepIceServers( server.password = JavaToStdString(jni, password); ice_servers->push_back(server); } - CHECK_EXCEPTION(jni, "error during CallBooleanMethod"); + CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; } JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)( @@ -2635,16 +3005,16 @@ JOW(jobject, PeerConnection_createDataChannel)( // vararg parameter as 64-bit and reading memory that doesn't belong to the // 32-bit parameter. jlong nativeChannelPtr = jlongFromPointer(channel.get()); - CHECK(nativeChannelPtr, "Failed to create DataChannel"); + CHECK(nativeChannelPtr) << "Failed to create DataChannel"; jclass j_data_channel_class = FindClass(jni, "org/webrtc/DataChannel"); jmethodID j_data_channel_ctor = GetMethodID( jni, j_data_channel_class, "<init>", "(J)V"); jobject j_channel = jni->NewObject( j_data_channel_class, j_data_channel_ctor, nativeChannelPtr); - CHECK_EXCEPTION(jni, "error during NewObject"); + CHECK_EXCEPTION(jni) << "error during NewObject"; // Channel is now owned by Java object, and will be freed from there. int bumped_count = channel->AddRef(); - CHECK(bumped_count == 2, "Unexpected refcount"); + CHECK(bumped_count == 2) << "Unexpected refcount"; return j_channel; } @@ -2680,7 +3050,7 @@ static SessionDescriptionInterface* JavaSdpToNativeSdp( "()Ljava/lang/String;"); jstring j_type_string = (jstring)jni->CallObjectMethod( j_type, j_canonical_form_id); - CHECK_EXCEPTION(jni, "error during CallObjectMethod"); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; std::string std_type = JavaToStdString(jni, j_type_string); jfieldID j_description_id = GetFieldID( @@ -2790,7 +3160,7 @@ JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)( std::string device_name = JavaToStdString(jni, j_device_name); scoped_ptr<cricket::DeviceManagerInterface> device_manager( cricket::DeviceManagerFactory::Create()); - CHECK(device_manager->Init(), "DeviceManager::Init() failed"); + CHECK(device_manager->Init()) << "DeviceManager::Init() failed"; cricket::Device device; if (!device_manager->GetVideoCaptureDevice(device_name, &device)) { LOG(LS_ERROR) << "GetVideoCaptureDevice failed for " << device_name; @@ -2826,8 +3196,8 @@ JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) { JOW(void, VideoSource_restart)( JNIEnv* jni, jclass, jlong j_p_source, jlong j_p_format) { - CHECK(j_p_source, ""); - CHECK(j_p_format, ""); + CHECK(j_p_source); + CHECK(j_p_format); scoped_ptr<cricket::VideoFormatPod> format( reinterpret_cast<cricket::VideoFormatPod*>(j_p_format)); reinterpret_cast<VideoSourceInterface*>(j_p_source)->GetVideoCapturer()-> diff --git a/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java b/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java index a6a059e..9280743 100644 --- a/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java +++ b/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java @@ -27,14 +27,24 @@ package org.webrtc; +import android.graphics.SurfaceTexture; import android.media.MediaCodec; -import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecList; import android.media.MediaFormat; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.view.Surface; + import java.nio.ByteBuffer; // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder. @@ -49,7 +59,7 @@ class MediaCodecVideoDecoder { private static final String TAG = "MediaCodecVideoDecoder"; - private static final int DEQUEUE_TIMEOUT = 1000000; // 1 sec timeout. + private static final int DEQUEUE_INPUT_TIMEOUT = 500000; // 500 ms timeout. private Thread mediaCodecThread; private MediaCodec mediaCodec; private ByteBuffer[] inputBuffers; @@ -57,7 +67,7 @@ class MediaCodecVideoDecoder { private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; // List of supported HW VP8 decoders. private static final String[] supportedHwCodecPrefixes = - {"OMX.Nvidia."}; + {"OMX.qcom.", "OMX.Nvidia." }; // NV12 color format supported by QCOM codec, but not declared in MediaCodec - // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h private static final int @@ -74,12 +84,21 @@ class MediaCodecVideoDecoder { private int height; private int stride; private int sliceHeight; + private boolean useSurface; + private int textureID = -1; + private SurfaceTexture surfaceTexture = null; + private Surface surface = null; + private float[] stMatrix = new float[16]; + private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext eglContext = EGL14.EGL_NO_CONTEXT; + private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE; + private MediaCodecVideoDecoder() { } // Helper struct for findVp8HwDecoder() below. private static class DecoderProperties { - DecoderProperties(String codecName, int colorFormat) { + public DecoderProperties(String codecName, int colorFormat) { this.codecName = codecName; this.colorFormat = colorFormat; } @@ -107,26 +126,32 @@ class MediaCodecVideoDecoder { continue; // No VP8 support in this codec; try the next one. } Log.d(TAG, "Found candidate decoder " + name); + + // Check if this is supported HW decoder. + boolean supportedCodec = false; + for (String hwCodecPrefix : supportedHwCodecPrefixes) { + if (name.startsWith(hwCodecPrefix)) { + supportedCodec = true; + break; + } + } + if (!supportedCodec) { + continue; + } + + // Check if codec supports either yuv420 or nv12. CodecCapabilities capabilities = info.getCapabilitiesForType(VP8_MIME_TYPE); for (int colorFormat : capabilities.colorFormats) { Log.d(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); } - - // Check if this is supported HW decoder - for (String hwCodecPrefix : supportedHwCodecPrefixes) { - if (!name.startsWith(hwCodecPrefix)) { - continue; - } - // Check if codec supports either yuv420 or nv12 - for (int supportedColorFormat : supportedColorList) { - for (int codecColorFormat : capabilities.colorFormats) { - if (codecColorFormat == supportedColorFormat) { - // Found supported HW VP8 decoder - Log.d(TAG, "Found target decoder " + name + - ". Color: 0x" + Integer.toHexString(codecColorFormat)); - return new DecoderProperties(name, codecColorFormat); - } + for (int supportedColorFormat : supportedColorList) { + for (int codecColorFormat : capabilities.colorFormats) { + if (codecColorFormat == supportedColorFormat) { + // Found supported HW VP8 decoder. + Log.d(TAG, "Found target decoder " + name + + ". Color: 0x" + Integer.toHexString(codecColorFormat)); + return new DecoderProperties(name, codecColorFormat); } } } @@ -146,31 +171,166 @@ class MediaCodecVideoDecoder { } } - private boolean initDecode(int width, int height) { + private void checkEglError(String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + Log.e(TAG, msg + ": EGL Error: 0x" + Integer.toHexString(error)); + throw new RuntimeException( + msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + + private void checkGlError(String msg) { + int error; + if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, msg + ": GL Error: 0x" + Integer.toHexString(error)); + throw new RuntimeException( + msg + ": GL Error: 0x " + Integer.toHexString(error)); + } + } + + private void eglSetup(EGLContext sharedContext, int width, int height) { + Log.d(TAG, "EGL setup"); + if (sharedContext == null) { + sharedContext = EGL14.EGL_NO_CONTEXT; + } + eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (eglDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("Unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) { + throw new RuntimeException("Unable to initialize EGL14"); + } + + // Configure EGL for pbuffer and OpenGL ES 2.0. + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, + configs.length, numConfigs, 0)) { + throw new RuntimeException("Unable to find RGB888 EGL config"); + } + + // Configure context for OpenGL ES 2.0. + int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], sharedContext, + attrib_list, 0); + checkEglError("eglCreateContext"); + if (eglContext == null) { + throw new RuntimeException("Null EGL context"); + } + + // Create a pbuffer surface. + int[] surfaceAttribs = { + EGL14.EGL_WIDTH, width, + EGL14.EGL_HEIGHT, height, + EGL14.EGL_NONE + }; + eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], + surfaceAttribs, 0); + checkEglError("eglCreatePbufferSurface"); + if (eglSurface == null) { + throw new RuntimeException("EGL surface was null"); + } + } + + private void eglRelease() { + Log.d(TAG, "EGL release"); + if (eglDisplay != EGL14.EGL_NO_DISPLAY) { + EGL14.eglDestroySurface(eglDisplay, eglSurface); + EGL14.eglDestroyContext(eglDisplay, eglContext); + EGL14.eglReleaseThread(); + EGL14.eglTerminate(eglDisplay); + } + eglDisplay = EGL14.EGL_NO_DISPLAY; + eglContext = EGL14.EGL_NO_CONTEXT; + eglSurface = EGL14.EGL_NO_SURFACE; + } + + + private void makeCurrent() { + if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + + private boolean initDecode(int width, int height, boolean useSurface, + EGLContext sharedContext) { if (mediaCodecThread != null) { throw new RuntimeException("Forgot to release()?"); } + if (useSurface && sharedContext == null) { + throw new RuntimeException("No shared EGL context."); + } DecoderProperties properties = findVp8HwDecoder(); if (properties == null) { throw new RuntimeException("Cannot find HW VP8 decoder"); } Log.d(TAG, "Java initDecode: " + width + " x " + height + - ". Color: 0x" + Integer.toHexString(properties.colorFormat)); + ". Color: 0x" + Integer.toHexString(properties.colorFormat) + + ". Use Surface: " + useSurface ); + if (sharedContext != null) { + Log.d(TAG, "Decoder shared EGL Context: " + sharedContext); + } mediaCodecThread = Thread.currentThread(); try { + Surface decodeSurface = null; this.width = width; this.height = height; + this.useSurface = useSurface; stride = width; sliceHeight = height; + + if (useSurface) { + // Create shared EGL context. + eglSetup(sharedContext, width, height); + makeCurrent(); + + // Create output surface + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + checkGlError("glGenTextures"); + textureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID); + checkGlError("glBindTexture mTextureID"); + + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + Log.d(TAG, "Video decoder TextureID = " + textureID); + surfaceTexture = new SurfaceTexture(textureID); + surface = new Surface(surfaceTexture); + decodeSurface = surface; + } + MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height); - format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); + if (!useSurface) { + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); + } Log.d(TAG, " Format: " + format); mediaCodec = MediaCodec.createByCodecName(properties.codecName); if (mediaCodec == null) { return false; } - mediaCodec.configure(format, null, null, 0); + mediaCodec.configure(format, decodeSurface, null, 0); mediaCodec.start(); colorFormat = properties.colorFormat; outputBuffers = mediaCodec.getOutputBuffers(); @@ -195,6 +355,19 @@ class MediaCodecVideoDecoder { } mediaCodec = null; mediaCodecThread = null; + if (useSurface) { + surface.release(); + surface = null; + surfaceTexture = null; + if (textureID >= 0) { + int[] textures = new int[1]; + textures[0] = textureID; + Log.d(TAG, "Delete video decoder TextureID " + textureID); + GLES20.glDeleteTextures(1, textures, 0); + checkGlError("glDeleteTextures"); + } + eglRelease(); + } } // Dequeue an input buffer and return its index, -1 if no input buffer is @@ -202,7 +375,7 @@ class MediaCodecVideoDecoder { private int dequeueInputBuffer() { checkOnMediaCodecThread(); try { - return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT); + return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT); } catch (IllegalStateException e) { Log.e(TAG, "dequeueIntputBuffer failed", e); return -2; @@ -224,23 +397,40 @@ class MediaCodecVideoDecoder { } } + // Helper struct for dequeueOutputBuffer() below. + private static class DecoderOutputBufferInfo { + public DecoderOutputBufferInfo( + int index, int offset, int size, long presentationTimestampUs) { + this.index = index; + this.offset = offset; + this.size = size; + this.presentationTimestampUs = presentationTimestampUs; + } + + private final int index; + private final int offset; + private final int size; + private final long presentationTimestampUs; + } + // Dequeue and return an output buffer index, -1 if no output // buffer available or -2 if error happened. - private int dequeueOutputBuffer() { + private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) { checkOnMediaCodecThread(); try { MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); - int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); + int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { outputBuffers = mediaCodec.getOutputBuffers(); + Log.d(TAG, "Output buffers changed: " + outputBuffers.length); } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat format = mediaCodec.getOutputFormat(); Log.d(TAG, "Format changed: " + format.toString()); width = format.getInteger(MediaFormat.KEY_WIDTH); height = format.getInteger(MediaFormat.KEY_HEIGHT); - if (format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { + if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT); Log.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat)); // Check if new color space is supported. @@ -253,7 +443,7 @@ class MediaCodecVideoDecoder { } if (!validColorFormat) { Log.e(TAG, "Non supported color format"); - return -2; + return new DecoderOutputBufferInfo(-1, 0, 0, -1); } } if (format.containsKey("stride")) { @@ -267,21 +457,28 @@ class MediaCodecVideoDecoder { stride = Math.max(width, stride); sliceHeight = Math.max(height, sliceHeight); } - result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); + result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); } - return result; + if (result >= 0) { + return new DecoderOutputBufferInfo(result, info.offset, info.size, + info.presentationTimeUs); + } + return null; } catch (IllegalStateException e) { Log.e(TAG, "dequeueOutputBuffer failed", e); - return -2; + return new DecoderOutputBufferInfo(-1, 0, 0, -1); } } // Release a dequeued output buffer back to the codec for re-use. Return // false if the codec is no longer operable. - private boolean releaseOutputBuffer(int index) { + private boolean releaseOutputBuffer(int index, boolean render) { checkOnMediaCodecThread(); try { - mediaCodec.releaseOutputBuffer(index, false); + if (!useSurface) { + render = false; + } + mediaCodec.releaseOutputBuffer(index, render); return true; } catch (IllegalStateException e) { Log.e(TAG, "releaseOutputBuffer failed", e); diff --git a/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java b/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java index 45b8d6a..cf11573 100644 --- a/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java +++ b/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java @@ -78,7 +78,7 @@ class MediaCodecVideoEncoder { // Helper struct for findVp8HwEncoder() below. private static class EncoderProperties { - EncoderProperties(String codecName, int colorFormat) { + public EncoderProperties(String codecName, int colorFormat) { this.codecName = codecName; this.colorFormat = colorFormat; } @@ -106,26 +106,33 @@ class MediaCodecVideoEncoder { continue; // No VP8 support in this codec; try the next one. } Log.d(TAG, "Found candidate encoder " + name); + + // Check if this is supported HW encoder. + boolean supportedCodec = false; + for (String hwCodecPrefix : supportedHwCodecPrefixes) { + if (name.startsWith(hwCodecPrefix)) { + supportedCodec = true; + break; + } + } + if (!supportedCodec) { + continue; + } + CodecCapabilities capabilities = info.getCapabilitiesForType(VP8_MIME_TYPE); for (int colorFormat : capabilities.colorFormats) { Log.d(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); } - // Check if this is supported HW encoder - for (String hwCodecPrefix : supportedHwCodecPrefixes) { - if (!name.startsWith(hwCodecPrefix)) { - continue; - } - // Check if codec supports either yuv420 or nv12 - for (int supportedColorFormat : supportedColorList) { - for (int codecColorFormat : capabilities.colorFormats) { - if (codecColorFormat == supportedColorFormat) { - // Found supported HW VP8 encoder - Log.d(TAG, "Found target encoder " + name + - ". Color: 0x" + Integer.toHexString(codecColorFormat)); - return new EncoderProperties(name, codecColorFormat); - } + // Check if codec supports either yuv420 or nv12. + for (int supportedColorFormat : supportedColorList) { + for (int codecColorFormat : capabilities.colorFormats) { + if (codecColorFormat == supportedColorFormat) { + // Found supported HW VP8 encoder. + Log.d(TAG, "Found target encoder " + name + + ". Color: 0x" + Integer.toHexString(codecColorFormat)); + return new EncoderProperties(name, codecColorFormat); } } } @@ -137,15 +144,6 @@ class MediaCodecVideoEncoder { return findVp8HwEncoder() != null; } - private static int bitRate(int kbps) { - // webrtc "kilo" means 1000, not 1024. Apparently. - // (and the price for overshooting is frame-dropping as webrtc enforces its - // bandwidth estimation, which is unpleasant). - // Since the HW encoder in the N5 overshoots, we clamp to a bit less than - // the requested rate. Sad but true. Bug 3194. - return kbps * 950; - } - private void checkOnMediaCodecThread() { if (mediaCodecThread.getId() != Thread.currentThread().getId()) { throw new RuntimeException( @@ -170,7 +168,7 @@ class MediaCodecVideoEncoder { try { MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height); - format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate(kbps)); + format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); // Default WebRTC settings @@ -241,7 +239,7 @@ class MediaCodecVideoEncoder { Log.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); try { Bundle params = new Bundle(); - params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitRate(kbps)); + params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps); mediaCodec.setParameters(params); return true; } catch (IllegalStateException e) { diff --git a/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java b/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java index 441f37b..f9f96e7 100644 --- a/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java +++ b/app/webrtc/java/src/org/webrtc/PeerConnectionFactory.java @@ -46,8 +46,12 @@ public class PeerConnectionFactory { // Callers may specify either |initializeAudio| or |initializeVideo| as false // to skip initializing the respective engine (and avoid the need for the // respective permissions). + // |renderEGLContext| can be provided to suport HW video decoding to + // texture and will be used to create a shared EGL context on video + // decoding thread. public static native boolean initializeAndroidGlobals( - Object context, boolean initializeAudio, boolean initializeVideo); + Object context, boolean initializeAudio, boolean initializeVideo, + Object renderEGLContext); public PeerConnectionFactory() { nativeFactory = nativeCreatePeerConnectionFactory(); diff --git a/app/webrtc/java/src/org/webrtc/VideoRenderer.java b/app/webrtc/java/src/org/webrtc/VideoRenderer.java index 4cc341a..19df137 100644 --- a/app/webrtc/java/src/org/webrtc/VideoRenderer.java +++ b/app/webrtc/java/src/org/webrtc/VideoRenderer.java @@ -44,6 +44,9 @@ public class VideoRenderer { public final int height; public final int[] yuvStrides; public final ByteBuffer[] yuvPlanes; + public final boolean yuvFrame; + public Object textureObject; + public int textureId; /** * Construct a frame of the given dimensions with the specified planar @@ -62,25 +65,72 @@ public class VideoRenderer { yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height); } this.yuvPlanes = yuvPlanes; + this.yuvFrame = true; + } + + /** + * Construct a texture frame of the given dimensions with data in SurfaceTexture + */ + public I420Frame( + int width, int height, Object textureObject, int textureId) { + this.width = width; + this.height = height; + this.yuvStrides = null; + this.yuvPlanes = null; + this.textureObject = textureObject; + this.textureId = textureId; + this.yuvFrame = false; } /** * Copy the planes out of |source| into |this| and return |this|. Calling - * this with mismatched frame dimensions is a programming error and will - * likely crash. + * this with mismatched frame dimensions or frame type is a programming + * error and will likely crash. */ public I420Frame copyFrom(I420Frame source) { - if (!Arrays.equals(yuvStrides, source.yuvStrides) || - width != source.width || height != source.height) { - throw new RuntimeException("Mismatched dimensions! Source: " + + if (source.yuvFrame && yuvFrame) { + if (!Arrays.equals(yuvStrides, source.yuvStrides) || + width != source.width || height != source.height) { + throw new RuntimeException("Mismatched dimensions! Source: " + + source.toString() + ", destination: " + toString()); + } + copyPlane(source.yuvPlanes[0], yuvPlanes[0]); + copyPlane(source.yuvPlanes[1], yuvPlanes[1]); + copyPlane(source.yuvPlanes[2], yuvPlanes[2]); + return this; + } else if (!source.yuvFrame && !yuvFrame) { + textureObject = source.textureObject; + textureId = source.textureId; + return this; + } else { + throw new RuntimeException("Mismatched frame types! Source: " + source.toString() + ", destination: " + toString()); } - copyPlane(source.yuvPlanes[0], yuvPlanes[0]); - copyPlane(source.yuvPlanes[1], yuvPlanes[1]); - copyPlane(source.yuvPlanes[2], yuvPlanes[2]); - return this; } + public I420Frame copyFrom(byte[] yuvData) { + if (yuvData.length < width * height * 3 / 2) { + throw new RuntimeException("Wrong arrays size: " + yuvData.length); + } + if (!yuvFrame) { + throw new RuntimeException("Can not feed yuv data to texture frame"); + } + int planeSize = width * height; + ByteBuffer[] planes = new ByteBuffer[3]; + planes[0] = ByteBuffer.wrap(yuvData, 0, planeSize); + planes[1] = ByteBuffer.wrap(yuvData, planeSize, planeSize / 4); + planes[2] = ByteBuffer.wrap(yuvData, planeSize + planeSize / 4, + planeSize / 4); + for (int i = 0; i < 3; i++) { + yuvPlanes[i].position(0); + yuvPlanes[i].put(planes[i]); + yuvPlanes[i].position(0); + yuvPlanes[i].limit(yuvPlanes[i].capacity()); + } + return this; + } + + @Override public String toString() { return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] + @@ -126,11 +176,16 @@ public class VideoRenderer { } public void dispose() { - free(nativeVideoRenderer); + if (callbacks == null) { + freeGuiVideoRenderer(nativeVideoRenderer); + } else { + freeWrappedVideoRenderer(nativeVideoRenderer); + } } private static native long nativeCreateGuiVideoRenderer(int x, int y); private static native long nativeWrapVideoRenderer(Callbacks callbacks); - private static native void free(long nativeVideoRenderer); + private static native void freeGuiVideoRenderer(long nativeVideoRenderer); + private static native void freeWrappedVideoRenderer(long nativeVideoRenderer); } diff --git a/app/webrtc/jsepsessiondescription.cc b/app/webrtc/jsepsessiondescription.cc index 23a7cdd..c318d91 100644 --- a/app/webrtc/jsepsessiondescription.cc +++ b/app/webrtc/jsepsessiondescription.cc @@ -57,11 +57,19 @@ const char SessionDescriptionInterface::kPrAnswer[] = "pranswer"; const char SessionDescriptionInterface::kAnswer[] = "answer"; const int JsepSessionDescription::kDefaultVideoCodecId = 100; -const int JsepSessionDescription::kDefaultVideoCodecFramerate = 30; +// This is effectively a max value of the frame rate. 30 is default from camera. +const int JsepSessionDescription::kDefaultVideoCodecFramerate = 60; const char JsepSessionDescription::kDefaultVideoCodecName[] = "VP8"; // Used as default max video codec size before we have it in signaling. -const int JsepSessionDescription::kMaxVideoCodecWidth = 3840; -const int JsepSessionDescription::kMaxVideoCodecHeight = 2160; +#if defined(ANDROID) +// Limit default max video codec size for Android to avoid +// HW VP8 codec initialization failure for resolution higher than 720p. +const int JsepSessionDescription::kMaxVideoCodecWidth = 1280; +const int JsepSessionDescription::kMaxVideoCodecHeight = 720; +#else +const int JsepSessionDescription::kMaxVideoCodecWidth = 1920; +const int JsepSessionDescription::kMaxVideoCodecHeight = 1080; +#endif const int JsepSessionDescription::kDefaultVideoCodecPreference = 1; SessionDescriptionInterface* CreateSessionDescription(const std::string& type, diff --git a/app/webrtc/mediaconstraintsinterface.cc b/app/webrtc/mediaconstraintsinterface.cc index d59716e..a7b88aa 100644 --- a/app/webrtc/mediaconstraintsinterface.cc +++ b/app/webrtc/mediaconstraintsinterface.cc @@ -95,6 +95,8 @@ const char MediaConstraintsInterface::kEnableVideoSuspendBelowMinBitrate[] = "googSuspendBelowMinBitrate"; const char MediaConstraintsInterface::kNumUnsignalledRecvStreams[] = "googNumUnsignalledRecvStreams"; +const char MediaConstraintsInterface::kCombinedAudioVideoBwe[] = + "googCombinedAudioVideoBwe"; const char MediaConstraintsInterface::kScreencastMinBitrate[] = "googScreencastMinBitrate"; // TODO(ronghuawu): Remove once cpu overuse detection is stable. diff --git a/app/webrtc/mediaconstraintsinterface.h b/app/webrtc/mediaconstraintsinterface.h index d74ffab..045da79 100644 --- a/app/webrtc/mediaconstraintsinterface.h +++ b/app/webrtc/mediaconstraintsinterface.h @@ -117,6 +117,8 @@ class MediaConstraintsInterface { // googSuspendBelowMinBitrate static const char kNumUnsignalledRecvStreams[]; // googNumUnsignalledRecvStreams + // Constraint to enable combined audio+video bandwidth estimation. + static const char kCombinedAudioVideoBwe[]; // googCombinedAudioVideoBwe static const char kScreencastMinBitrate[]; // googScreencastMinBitrate static const char kCpuOveruseDetection[]; // googCpuOveruseDetection static const char kCpuUnderuseThreshold[]; // googCpuUnderuseThreshold diff --git a/app/webrtc/mediastreamsignaling.cc b/app/webrtc/mediastreamsignaling.cc index c4a8281..df51ba1 100644 --- a/app/webrtc/mediastreamsignaling.cc +++ b/app/webrtc/mediastreamsignaling.cc @@ -51,9 +51,9 @@ namespace webrtc { using rtc::scoped_ptr; using rtc::scoped_refptr; -static bool ParseConstraints( +static bool ParseConstraintsForAnswer( const MediaConstraintsInterface* constraints, - cricket::MediaSessionOptions* options, bool is_answer) { + cricket::MediaSessionOptions* options) { bool value; size_t mandatory_constraints_satisfied = 0; @@ -82,7 +82,7 @@ static bool ParseConstraints( // kOfferToReceiveVideo defaults to false according to spec. But // if it is an answer and video is offered, we should still accept video // per default. - options->has_video |= is_answer; + options->has_video = true; } if (FindConstraint(constraints, @@ -133,6 +133,55 @@ static bool IsValidOfferToReceiveMedia(int value) { (value <= Options::kMaxOfferToReceiveMedia); } +// Add the stream and RTP data channel info to |session_options|. +static void SetStreams( + cricket::MediaSessionOptions* session_options, + rtc::scoped_refptr<StreamCollection> streams, + const MediaStreamSignaling::RtpDataChannels& rtp_data_channels) { + session_options->streams.clear(); + if (streams != NULL) { + for (size_t i = 0; i < streams->count(); ++i) { + MediaStreamInterface* stream = streams->at(i); + + AudioTrackVector audio_tracks(stream->GetAudioTracks()); + + // For each audio track in the stream, add it to the MediaSessionOptions. + for (size_t j = 0; j < audio_tracks.size(); ++j) { + scoped_refptr<MediaStreamTrackInterface> track(audio_tracks[j]); + session_options->AddStream( + cricket::MEDIA_TYPE_AUDIO, track->id(), stream->label()); + } + + VideoTrackVector video_tracks(stream->GetVideoTracks()); + + // For each video track in the stream, add it to the MediaSessionOptions. + for (size_t j = 0; j < video_tracks.size(); ++j) { + scoped_refptr<MediaStreamTrackInterface> track(video_tracks[j]); + session_options->AddStream( + cricket::MEDIA_TYPE_VIDEO, track->id(), stream->label()); + } + } + } + + // Check for data channels. + MediaStreamSignaling::RtpDataChannels::const_iterator data_channel_it = + rtp_data_channels.begin(); + for (; data_channel_it != rtp_data_channels.end(); ++data_channel_it) { + const DataChannel* channel = data_channel_it->second; + if (channel->state() == DataChannel::kConnecting || + channel->state() == DataChannel::kOpen) { + // |streamid| and |sync_label| are both set to the DataChannel label + // here so they can be signaled the same way as MediaStreams and Tracks. + // For MediaStreams, the sync_label is the MediaStream label and the + // track label is the same as |streamid|. + const std::string& streamid = channel->label(); + const std::string& sync_label = channel->label(); + session_options->AddStream( + cricket::MEDIA_TYPE_DATA, streamid, sync_label); + } + } +} + // Factory class for creating remote MediaStreams and MediaStreamTracks. class RemoteMediaStreamFactory { public: @@ -192,8 +241,6 @@ MediaStreamSignaling::MediaStreamSignaling( channel_manager)), last_allocated_sctp_even_sid_(-2), last_allocated_sctp_odd_sid_(-1) { - options_.has_video = false; - options_.has_audio = false; } MediaStreamSignaling::~MediaStreamSignaling() { @@ -279,12 +326,13 @@ bool MediaStreamSignaling::AddDataChannelFromOpenMessage( LOG(LS_ERROR) << "Failed to create DataChannel from the OPEN message."; return false; } - sctp_data_channels_.push_back(channel); + stream_observer_->OnAddDataChannel(channel); return true; } void MediaStreamSignaling::RemoveSctpDataChannel(int sid) { + ASSERT(sid >= 0); for (SctpDataChannels::iterator iter = sctp_data_channels_.begin(); iter != sctp_data_channels_.end(); ++iter) { @@ -377,40 +425,38 @@ bool MediaStreamSignaling::GetOptionsForOffer( return false; } - UpdateSessionOptions(); + session_options->has_audio = false; + session_options->has_video = false; + SetStreams(session_options, local_streams_, rtp_data_channels_); - // |options.has_audio| and |options.has_video| can only change from false to - // true, but never change from true to false. This is to make sure - // CreateOffer / CreateAnswer doesn't remove a media content - // description that has been created. - if (rtc_options.offer_to_receive_audio > 0) { - options_.has_audio = true; + // If |offer_to_receive_[audio/video]| is undefined, respect the flags set + // from SetStreams. Otherwise, overwrite it based on |rtc_options|. + if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { + session_options->has_audio = rtc_options.offer_to_receive_audio > 0; } - if (rtc_options.offer_to_receive_video > 0) { - options_.has_video = true; + if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { + session_options->has_video = rtc_options.offer_to_receive_video > 0; } - options_.vad_enabled = rtc_options.voice_activity_detection; - options_.transport_options.ice_restart = rtc_options.ice_restart; - options_.bundle_enabled = rtc_options.use_rtp_mux; - options_.bundle_enabled = EvaluateNeedForBundle(options_); - *session_options = options_; + session_options->vad_enabled = rtc_options.voice_activity_detection; + session_options->transport_options.ice_restart = rtc_options.ice_restart; + session_options->bundle_enabled = rtc_options.use_rtp_mux; + + session_options->bundle_enabled = EvaluateNeedForBundle(*session_options); return true; } bool MediaStreamSignaling::GetOptionsForAnswer( const MediaConstraintsInterface* constraints, cricket::MediaSessionOptions* options) { - UpdateSessionOptions(); + options->has_audio = false; + options->has_video = false; + SetStreams(options, local_streams_, rtp_data_channels_); - // Copy the |options_| to not let the flag MediaSessionOptions::has_audio and - // MediaSessionOptions::has_video affect subsequent offers. - cricket::MediaSessionOptions current_options = options_; - if (!ParseConstraints(constraints, ¤t_options, true)) { + if (!ParseConstraintsForAnswer(constraints, options)) { return false; } - current_options.bundle_enabled = EvaluateNeedForBundle(current_options); - *options = current_options; + options->bundle_enabled = EvaluateNeedForBundle(*options); return true; } @@ -545,54 +591,6 @@ void MediaStreamSignaling::OnDataChannelClose() { } } -void MediaStreamSignaling::UpdateSessionOptions() { - options_.streams.clear(); - if (local_streams_ != NULL) { - for (size_t i = 0; i < local_streams_->count(); ++i) { - MediaStreamInterface* stream = local_streams_->at(i); - - AudioTrackVector audio_tracks(stream->GetAudioTracks()); - if (!audio_tracks.empty()) { - options_.has_audio = true; - } - - // For each audio track in the stream, add it to the MediaSessionOptions. - for (size_t j = 0; j < audio_tracks.size(); ++j) { - scoped_refptr<MediaStreamTrackInterface> track(audio_tracks[j]); - options_.AddStream(cricket::MEDIA_TYPE_AUDIO, track->id(), - stream->label()); - } - - VideoTrackVector video_tracks(stream->GetVideoTracks()); - if (!video_tracks.empty()) { - options_.has_video = true; - } - // For each video track in the stream, add it to the MediaSessionOptions. - for (size_t j = 0; j < video_tracks.size(); ++j) { - scoped_refptr<MediaStreamTrackInterface> track(video_tracks[j]); - options_.AddStream(cricket::MEDIA_TYPE_VIDEO, track->id(), - stream->label()); - } - } - } - - // Check for data channels. - RtpDataChannels::const_iterator data_channel_it = rtp_data_channels_.begin(); - for (; data_channel_it != rtp_data_channels_.end(); ++data_channel_it) { - const DataChannel* channel = data_channel_it->second; - if (channel->state() == DataChannel::kConnecting || - channel->state() == DataChannel::kOpen) { - // |streamid| and |sync_label| are both set to the DataChannel label - // here so they can be signaled the same way as MediaStreams and Tracks. - // For MediaStreams, the sync_label is the MediaStream label and the - // track label is the same as |streamid|. - const std::string& streamid = channel->label(); - const std::string& sync_label = channel->label(); - options_.AddStream(cricket::MEDIA_TYPE_DATA, streamid, sync_label); - } - } -} - void MediaStreamSignaling::UpdateRemoteStreamsList( const cricket::StreamParamsVec& streams, cricket::MediaType media_type, diff --git a/app/webrtc/mediastreamsignaling.h b/app/webrtc/mediastreamsignaling.h index 7f17971..d4b1be8 100644 --- a/app/webrtc/mediastreamsignaling.h +++ b/app/webrtc/mediastreamsignaling.h @@ -160,6 +160,9 @@ class MediaStreamSignalingObserver { class MediaStreamSignaling : public sigslot::has_slots<> { public: + typedef std::map<std::string, rtc::scoped_refptr<DataChannel> > + RtpDataChannels; + MediaStreamSignaling(rtc::Thread* signaling_thread, MediaStreamSignalingObserver* stream_observer, cricket::ChannelManager* channel_manager); @@ -289,8 +292,6 @@ class MediaStreamSignaling : public sigslot::has_slots<> { }; typedef std::vector<TrackInfo> TrackInfos; - void UpdateSessionOptions(); - // Makes sure a MediaStream Track is created for each StreamParam in // |streams|. |media_type| is the type of the |streams| and can be either // audio or video. @@ -378,7 +379,6 @@ class MediaStreamSignaling : public sigslot::has_slots<> { RemotePeerInfo remote_info_; rtc::Thread* signaling_thread_; DataChannelFactory* data_channel_factory_; - cricket::MediaSessionOptions options_; MediaStreamSignalingObserver* stream_observer_; rtc::scoped_refptr<StreamCollection> local_streams_; rtc::scoped_refptr<StreamCollection> remote_streams_; @@ -392,8 +392,6 @@ class MediaStreamSignaling : public sigslot::has_slots<> { int last_allocated_sctp_even_sid_; int last_allocated_sctp_odd_sid_; - typedef std::map<std::string, rtc::scoped_refptr<DataChannel> > - RtpDataChannels; typedef std::vector<rtc::scoped_refptr<DataChannel> > SctpDataChannels; RtpDataChannels rtp_data_channels_; diff --git a/app/webrtc/mediastreamsignaling_unittest.cc b/app/webrtc/mediastreamsignaling_unittest.cc index fa83646..84f67b9 100644 --- a/app/webrtc/mediastreamsignaling_unittest.cc +++ b/app/webrtc/mediastreamsignaling_unittest.cc @@ -261,14 +261,20 @@ static bool CompareStreamCollections(StreamCollectionInterface* s1, class FakeDataChannelFactory : public webrtc::DataChannelFactory { public: FakeDataChannelFactory(FakeDataChannelProvider* provider, - cricket::DataChannelType dct) - : provider_(provider), type_(dct) {} + cricket::DataChannelType dct, + webrtc::MediaStreamSignaling* media_stream_signaling) + : provider_(provider), + type_(dct), + media_stream_signaling_(media_stream_signaling) {} virtual rtc::scoped_refptr<webrtc::DataChannel> CreateDataChannel( const std::string& label, const webrtc::InternalDataChannelInit* config) { last_init_ = *config; - return webrtc::DataChannel::Create(provider_, type_, label, *config); + rtc::scoped_refptr<webrtc::DataChannel> data_channel = + webrtc::DataChannel::Create(provider_, type_, label, *config); + media_stream_signaling_->AddDataChannel(data_channel); + return data_channel; } const webrtc::InternalDataChannelInit& last_init() const { @@ -278,6 +284,7 @@ class FakeDataChannelFactory : public webrtc::DataChannelFactory { private: FakeDataChannelProvider* provider_; cricket::DataChannelType type_; + webrtc::MediaStreamSignaling* media_stream_signaling_; webrtc::InternalDataChannelInit last_init_; }; @@ -775,8 +782,10 @@ TEST_F(MediaStreamSignalingTest, MediaConstraintsInAnswer) { RTCOfferAnswerOptions default_rtc_options; EXPECT_TRUE(signaling_->GetOptionsForOffer(default_rtc_options, &updated_offer_options)); - EXPECT_TRUE(updated_offer_options.has_audio); - EXPECT_TRUE(updated_offer_options.has_video); + // By default, |has_audio| or |has_video| are false if there is no media + // track. + EXPECT_FALSE(updated_offer_options.has_audio); + EXPECT_FALSE(updated_offer_options.has_video); } // This test verifies that the remote MediaStreams corresponding to a received @@ -1263,7 +1272,8 @@ TEST_F(MediaStreamSignalingTest, SctpDuplicatedLabelAllowed) { // message. TEST_F(MediaStreamSignalingTest, CreateDataChannelFromOpenMessage) { FakeDataChannelFactory fake_factory(data_channel_provider_.get(), - cricket::DCT_SCTP); + cricket::DCT_SCTP, + signaling_.get()); signaling_->SetDataChannelFactory(&fake_factory); webrtc::DataChannelInit config; config.id = 1; @@ -1283,7 +1293,8 @@ TEST_F(MediaStreamSignalingTest, DuplicatedLabelFromOpenMessageAllowed) { AddDataChannel(cricket::DCT_SCTP, "a", -1); FakeDataChannelFactory fake_factory(data_channel_provider_.get(), - cricket::DCT_SCTP); + cricket::DCT_SCTP, + signaling_.get()); signaling_->SetDataChannelFactory(&fake_factory); webrtc::DataChannelInit config; config.id = 0; @@ -1312,3 +1323,24 @@ TEST_F(MediaStreamSignalingTest, signaling_->OnRemoteSctpDataChannelClosed(config.id); EXPECT_EQ(webrtc::DataChannelInterface::kClosed, data_channel->state()); } + +// Verifies that DataChannel added from OPEN message is added to +// MediaStreamSignaling only once (webrtc issue 3778). +TEST_F(MediaStreamSignalingTest, DataChannelFromOpenMessageAddedOnce) { + FakeDataChannelFactory fake_factory(data_channel_provider_.get(), + cricket::DCT_SCTP, + signaling_.get()); + signaling_->SetDataChannelFactory(&fake_factory); + webrtc::DataChannelInit config; + config.id = 1; + rtc::Buffer payload; + webrtc::WriteDataChannelOpenMessage("a", config, &payload); + cricket::ReceiveDataParams params; + params.ssrc = config.id; + EXPECT_TRUE(signaling_->AddDataChannelFromOpenMessage(params, payload)); + EXPECT_TRUE(signaling_->HasDataChannels()); + + // Removes the DataChannel and verifies that no DataChannel is left. + signaling_->RemoveSctpDataChannel(config.id); + EXPECT_FALSE(signaling_->HasDataChannels()); +} diff --git a/app/webrtc/objc/RTCEAGLVideoView.m b/app/webrtc/objc/RTCEAGLVideoView.m index 5365d98..faacef6 100644 --- a/app/webrtc/objc/RTCEAGLVideoView.m +++ b/app/webrtc/objc/RTCEAGLVideoView.m @@ -173,6 +173,7 @@ return; } [_videoTrack removeRenderer:_videoRenderer]; + self.i420Frame = nil; _videoTrack = videoTrack; [_videoTrack addRenderer:_videoRenderer]; // TODO(tkchin): potentially handle changes in track state - e.g. render @@ -191,11 +192,9 @@ // This method is called when the GLKView's content is dirty and needs to be // redrawn. This occurs on main thread. - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { - if (self.i420Frame) { - // The renderer will draw the frame to the framebuffer corresponding to the - // one used by |view|. - [_glRenderer drawFrame:self.i420Frame]; - } + // The renderer will draw the frame to the framebuffer corresponding to the + // one used by |view|. + [_glRenderer drawFrame:self.i420Frame]; } #pragma mark - Private diff --git a/app/webrtc/objc/RTCNSGLVideoView.m b/app/webrtc/objc/RTCNSGLVideoView.m index 39f3678..292e792 100644 --- a/app/webrtc/objc/RTCNSGLVideoView.m +++ b/app/webrtc/objc/RTCNSGLVideoView.m @@ -116,6 +116,9 @@ static CVReturn OnDisplayLinkFired(CVDisplayLinkRef displayLink, if (_videoTrack) { [_videoTrack removeRenderer:_videoRenderer]; CVDisplayLinkStop(_displayLink); + // Clear contents. + self.i420Frame = nil; + [self drawFrame]; } _videoTrack = videoTrack; if (_videoTrack) { @@ -144,7 +147,7 @@ static CVReturn OnDisplayLinkFired(CVDisplayLinkRef displayLink, - (void)drawFrame { RTCI420Frame* i420Frame = self.i420Frame; - if (i420Frame && self.glRenderer.lastDrawnFrame != i420Frame) { + if (self.glRenderer.lastDrawnFrame != i420Frame) { // This method may be called from CVDisplayLink callback which isn't on the // main thread so we have to lock the GL context before drawing. CGLLockContext([[self openGLContext] CGLContextObj]); diff --git a/app/webrtc/objc/RTCOpenGLVideoRenderer.mm b/app/webrtc/objc/RTCOpenGLVideoRenderer.mm index 9ee0216..5a24cf0 100644 --- a/app/webrtc/objc/RTCOpenGLVideoRenderer.mm +++ b/app/webrtc/objc/RTCOpenGLVideoRenderer.mm @@ -205,16 +205,18 @@ static const GLsizei kNumTextures = 3 * kNumTextureSets; return NO; } [self ensureGLContext]; - if (![self updateTextureSizesForFrame:frame] || - ![self updateTextureDataForFrame:frame]) { - return NO; - } glClear(GL_COLOR_BUFFER_BIT); + if (frame) { + if (![self updateTextureSizesForFrame:frame] || + ![self updateTextureDataForFrame:frame]) { + return NO; + } #if !TARGET_OS_IPHONE - glBindVertexArray(_vertexArray); + glBindVertexArray(_vertexArray); #endif - glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } #if !TARGET_OS_IPHONE [_context flushBuffer]; #endif @@ -238,7 +240,6 @@ static const GLsizei kNumTextures = 3 * kNumTextureSets; } glUseProgram(_program); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glClearColor(0, 0, 0, 1); _isInitialized = YES; } diff --git a/app/webrtc/objc/RTCVideoRenderer.mm b/app/webrtc/objc/RTCVideoRenderer.mm index de03a1e..4cfe43a 100644 --- a/app/webrtc/objc/RTCVideoRenderer.mm +++ b/app/webrtc/objc/RTCVideoRenderer.mm @@ -30,10 +30,6 @@ #endif #import "RTCVideoRenderer+Internal.h" - -#if TARGET_OS_IPHONE -#import "RTCEAGLVideoView+Internal.h" -#endif #import "RTCI420Frame+Internal.h" namespace webrtc { @@ -62,9 +58,6 @@ class RTCVideoRendererAdapter : public VideoRendererInterface { @implementation RTCVideoRenderer { rtc::scoped_ptr<webrtc::RTCVideoRendererAdapter> _adapter; -#if TARGET_OS_IPHONE - RTCEAGLVideoView* _videoView; -#endif } - (instancetype)initWithDelegate:(id<RTCVideoRendererDelegate>)delegate { @@ -75,22 +68,6 @@ class RTCVideoRendererAdapter : public VideoRendererInterface { return self; } -#if TARGET_OS_IPHONE -// TODO(tkchin): remove shim for deprecated method. -- (instancetype)initWithView:(UIView*)view { - if (self = [super init]) { - _videoView = [[RTCEAGLVideoView alloc] initWithFrame:view.bounds]; - _videoView.autoresizingMask = - UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - _videoView.translatesAutoresizingMaskIntoConstraints = YES; - [view addSubview:_videoView]; - self.delegate = _videoView; - _adapter.reset(new webrtc::RTCVideoRendererAdapter(self)); - } - return self; -} -#endif - @end @implementation RTCVideoRenderer (Internal) diff --git a/app/webrtc/objc/public/RTCVideoRenderer.h b/app/webrtc/objc/public/RTCVideoRenderer.h index f78746c..37977ce 100644 --- a/app/webrtc/objc/public/RTCVideoRenderer.h +++ b/app/webrtc/objc/public/RTCVideoRenderer.h @@ -55,12 +55,6 @@ // of frames. - (instancetype)initWithDelegate:(id<RTCVideoRendererDelegate>)delegate; -#if TARGET_OS_IPHONE -// DEPRECATED. See https://code.google.com/p/webrtc/issues/detail?id=3341 for -// details. -- (instancetype)initWithView:(UIView*)view; -#endif - #ifndef DOXYGEN_SHOULD_SKIP_THIS // Disallow init and don't add to documentation - (id)init __attribute__(( diff --git a/app/webrtc/peerconnection.cc b/app/webrtc/peerconnection.cc index 201269a..d939f98 100644 --- a/app/webrtc/peerconnection.cc +++ b/app/webrtc/peerconnection.cc @@ -39,6 +39,7 @@ #include "talk/session/media/channelmanager.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" +#include "webrtc/system_wrappers/interface/field_trial.h" namespace { @@ -353,10 +354,14 @@ bool PeerConnection::DoInitialize( cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET; bool value; + // If IPv6 flag was specified, we'll not override it by experiment. if (FindConstraint( - constraints, - MediaConstraintsInterface::kEnableIPv6, - &value, NULL) && value) { + constraints, MediaConstraintsInterface::kEnableIPv6, &value, NULL)) { + if (value) { + portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_IPV6; + } + } else if (webrtc::field_trial::FindFullName("WebRTC-IPv6Default") == + "Enabled") { portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_IPV6; } @@ -508,10 +513,6 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, return; } RTCOfferAnswerOptions options; - // Defaults to receiving audio and not receiving video. - options.offer_to_receive_audio = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - options.offer_to_receive_video = 0; bool value; size_t mandatory_constraints = 0; @@ -672,7 +673,7 @@ bool PeerConnection::UpdateIce(const RTCConfiguration& config) { } } } - return session_->UpdateIce(config.type); + return session_->SetIceTransports(config.type); } bool PeerConnection::AddIceCandidate( diff --git a/app/webrtc/peerconnection_unittest.cc b/app/webrtc/peerconnection_unittest.cc index ca022c6..977fc11 100644 --- a/app/webrtc/peerconnection_unittest.cc +++ b/app/webrtc/peerconnection_unittest.cc @@ -90,6 +90,7 @@ static const int kMaxWaitMs = 2000; // warnings. #if !defined(THREAD_SANITIZER) static const int kMaxWaitForStatsMs = 3000; +static const int kMaxWaitForRembMs = 5000; #endif static const int kMaxWaitForFramesMs = 10000; static const int kEndAudioFrameCount = 3; @@ -154,11 +155,11 @@ class PeerConnectionTestClientBase } void AddMediaStream(bool audio, bool video) { - std::string label = kStreamLabelBase + + std::string stream_label = kStreamLabelBase + rtc::ToString<int>( static_cast<int>(peer_connection_->local_streams()->count())); rtc::scoped_refptr<webrtc::MediaStreamInterface> stream = - peer_connection_factory_->CreateLocalMediaStream(label); + peer_connection_factory_->CreateLocalMediaStream(stream_label); if (audio && can_receive_audio()) { FakeConstraints constraints; @@ -169,13 +170,13 @@ class PeerConnectionTestClientBase peer_connection_factory_->CreateAudioSource(&constraints); // TODO(perkj): Test audio source when it is implemented. Currently audio // always use the default input. + std::string label = stream_label + kAudioTrackLabelBase; rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( - peer_connection_factory_->CreateAudioTrack(kAudioTrackLabelBase, - source)); + peer_connection_factory_->CreateAudioTrack(label, source)); stream->AddTrack(audio_track); } if (video && can_receive_video()) { - stream->AddTrack(CreateLocalVideoTrack(label)); + stream->AddTrack(CreateLocalVideoTrack(stream_label)); } EXPECT_TRUE(peer_connection_->AddStream(stream, NULL)); @@ -368,6 +369,16 @@ class PeerConnectionTestClientBase return observer->BytesSent(); } + int GetAvailableReceivedBandwidthStats() { + rtc::scoped_refptr<MockStatsObserver> + observer(new rtc::RefCountedObject<MockStatsObserver>()); + EXPECT_TRUE(peer_connection_->GetStats( + observer, NULL, PeerConnectionInterface::kStatsOutputLevelStandard)); + EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs); + int bw = observer->AvailableReceiveBandwidth(); + return bw; + } + int rendered_width() { EXPECT_FALSE(fake_video_renderers_.empty()); return fake_video_renderers_.empty() ? 1 : @@ -447,6 +458,12 @@ class PeerConnectionTestClientBase webrtc::PeerConnectionInterface* pc() { return peer_connection_.get(); } + void StopVideoCapturers() { + for (std::vector<cricket::VideoCapturer*>::iterator it = + video_capturers_.begin(); it != video_capturers_.end(); ++it) { + (*it)->Stop(); + } + } protected: explicit PeerConnectionTestClientBase(const std::string& id) @@ -464,9 +481,8 @@ class PeerConnectionTestClientBase if (!allocator_factory_) { return false; } - audio_thread_.Start(); fake_audio_capture_module_ = FakeAudioCaptureModule::Create( - &audio_thread_); + rtc::Thread::Current()); if (fake_audio_capture_module_ == NULL) { return false; @@ -529,21 +545,17 @@ class PeerConnectionTestClientBase FakeConstraints source_constraints = video_constraints_; source_constraints.SetMandatoryMaxFrameRate(10); + cricket::FakeVideoCapturer* fake_capturer = + new webrtc::FakePeriodicVideoCapturer(); + video_capturers_.push_back(fake_capturer); rtc::scoped_refptr<webrtc::VideoSourceInterface> source = peer_connection_factory_->CreateVideoSource( - new webrtc::FakePeriodicVideoCapturer(), - &source_constraints); + fake_capturer, &source_constraints); std::string label = stream_label + kVideoTrackLabelBase; return peer_connection_factory_->CreateVideoTrack(label, source); } std::string id_; - // Separate thread for executing |fake_audio_capture_module_| tasks. Audio - // processing must not be performed on the same thread as signaling due to - // signaling time constraints and relative complexity of the audio pipeline. - // This is consistent with the video pipeline that us a a separate thread for - // encoding and decoding. - rtc::Thread audio_thread_; rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> allocator_factory_; @@ -569,6 +581,10 @@ class PeerConnectionTestClientBase // For remote peer communication. MessageReceiver* signaling_message_receiver_; + + // Store references to the video capturers we've created, so that we can stop + // them, if required. + std::vector<cricket::VideoCapturer*> video_capturers_; }; class JsepTestClient @@ -591,7 +607,7 @@ class JsepTestClient } virtual void Negotiate(bool audio, bool video) { rtc::scoped_ptr<SessionDescriptionInterface> offer; - EXPECT_TRUE(DoCreateOffer(offer.use())); + ASSERT_TRUE(DoCreateOffer(offer.use())); if (offer->description()->GetContentByName("audio")) { offer->description()->GetContentByName("audio")->rejected = !audio; @@ -1019,6 +1035,30 @@ class P2PTestConductor : public testing::Test { } } + // Wait until 'size' bytes of audio has been seen by the receiver, on the + // first audio stream. + void WaitForAudioData(int size) { + const int kMaxWaitForAudioDataMs = 10000; + + StreamCollectionInterface* local_streams = + initializing_client()->local_streams(); + ASSERT_GT(local_streams->count(), 0u); + ASSERT_GT(local_streams->at(0)->GetAudioTracks().size(), 0u); + MediaStreamTrackInterface* local_audio_track = + local_streams->at(0)->GetAudioTracks()[0]; + + // Wait until *any* audio has been received. + EXPECT_TRUE_WAIT( + receiving_client()->GetBytesReceivedStats(local_audio_track) > 0, + kMaxWaitForAudioDataMs); + + // Wait until 'size' number of bytes have been received. + size += receiving_client()->GetBytesReceivedStats(local_audio_track); + EXPECT_TRUE_WAIT( + receiving_client()->GetBytesReceivedStats(local_audio_track) > size, + kMaxWaitForAudioDataMs); + } + SignalingClass* initializing_client() { return initiating_client_.get(); } SignalingClass* receiving_client() { return receiving_client_.get(); } @@ -1314,6 +1354,7 @@ TEST_F(JsepPeerConnectionP2PTestClient, RegisterDataChannelObserver) { // Unregister the existing observer. receiving_client()->data_channel()->UnregisterObserver(); + std::string data = "hello world"; SendRtpData(initializing_client()->data_channel(), data); @@ -1437,4 +1478,69 @@ TEST_F(JsepPeerConnectionP2PTestClient, EnableVideoDecoderFactory(); LocalP2PTest(); } + +// Test receive bandwidth stats with only audio enabled at receiver. +TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsAudio) { + ASSERT_TRUE(CreateTestClients()); + receiving_client()->SetReceiveAudioVideo(true, false); + LocalP2PTest(); + + // Wait until we have received some audio data. Following REMB shoud be zero. + WaitForAudioData(10000); + EXPECT_EQ_WAIT( + receiving_client()->GetAvailableReceivedBandwidthStats(), 0, + kMaxWaitForRembMs); +} + +// Test receive bandwidth stats with combined BWE. +TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsCombined) { + FakeConstraints setup_constraints; + setup_constraints.AddOptional( + MediaConstraintsInterface::kCombinedAudioVideoBwe, true); + ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); + initializing_client()->AddMediaStream(true, true); + initializing_client()->AddMediaStream(false, true); + initializing_client()->AddMediaStream(false, true); + initializing_client()->AddMediaStream(false, true); + LocalP2PTest(); + + // Run until a non-zero bw is reported. + EXPECT_TRUE_WAIT(receiving_client()->GetAvailableReceivedBandwidthStats() > 0, + kMaxWaitForRembMs); + + // Halt video capturers, then run until we have gotten some audio. Following + // REMB should be non-zero. + initializing_client()->StopVideoCapturers(); + WaitForAudioData(10000); + EXPECT_TRUE_WAIT( + receiving_client()->GetAvailableReceivedBandwidthStats() > 0, + kMaxWaitForRembMs); +} + +// Test receive bandwidth stats with 1 video, 3 audio streams but no combined +// BWE. +TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsNotCombined) { + FakeConstraints setup_constraints; + setup_constraints.AddOptional( + MediaConstraintsInterface::kCombinedAudioVideoBwe, false); + ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints)); + initializing_client()->AddMediaStream(true, true); + initializing_client()->AddMediaStream(false, true); + initializing_client()->AddMediaStream(false, true); + initializing_client()->AddMediaStream(false, true); + LocalP2PTest(); + + // Run until a non-zero bw is reported. + EXPECT_TRUE_WAIT(receiving_client()->GetAvailableReceivedBandwidthStats() > 0, + kMaxWaitForRembMs); + + // Halt video capturers, then run until we have gotten some audio. Following + // REMB should be zero. + initializing_client()->StopVideoCapturers(); + WaitForAudioData(10000); + EXPECT_EQ_WAIT( + receiving_client()->GetAvailableReceivedBandwidthStats(), 0, + kMaxWaitForRembMs); +} + #endif // if !defined(THREAD_SANITIZER) diff --git a/app/webrtc/peerconnectionfactory.cc b/app/webrtc/peerconnectionfactory.cc index 5dccba8..862ceda 100644 --- a/app/webrtc/peerconnectionfactory.cc +++ b/app/webrtc/peerconnectionfactory.cc @@ -41,6 +41,7 @@ #include "talk/media/webrtc/webrtcmediaengine.h" #include "talk/media/webrtc/webrtcvideodecoderfactory.h" #include "talk/media/webrtc/webrtcvideoencoderfactory.h" +#include "webrtc/base/bind.h" #include "webrtc/modules/audio_device/include/audio_device.h" using rtc::scoped_refptr; diff --git a/app/webrtc/statstypes.h b/app/webrtc/statstypes.h index 8eae1ad..656b83c 100644 --- a/app/webrtc/statstypes.h +++ b/app/webrtc/statstypes.h @@ -36,6 +36,7 @@ #include <vector> #include "webrtc/base/basictypes.h" +#include "webrtc/base/common.h" #include "webrtc/base/stringencode.h" namespace webrtc { diff --git a/app/webrtc/test/fakeaudiocapturemodule.cc b/app/webrtc/test/fakeaudiocapturemodule.cc index ff45f14..2ad3f0f 100644 --- a/app/webrtc/test/fakeaudiocapturemodule.cc +++ b/app/webrtc/test/fakeaudiocapturemodule.cc @@ -94,13 +94,6 @@ int FakeAudioCaptureModule::frames_received() const { return frames_received_; } -int32_t FakeAudioCaptureModule::Version(char* /*version*/, - uint32_t& /*remaining_buffer_in_bytes*/, - uint32_t& /*position*/) const { - ASSERT(false); - return 0; -} - int32_t FakeAudioCaptureModule::TimeUntilNextProcess() { const uint32 current_time = rtc::Time(); if (current_time < last_process_time_ms_) { @@ -325,12 +318,6 @@ int32_t FakeAudioCaptureModule::WaveOutVolume( return 0; } -int32_t FakeAudioCaptureModule::SpeakerIsAvailable(bool* available) { - // No speaker, just dropping audio. Return success. - *available = true; - return 0; -} - int32_t FakeAudioCaptureModule::InitSpeaker() { // No speaker, just playing from file. Return success. return 0; @@ -341,12 +328,6 @@ bool FakeAudioCaptureModule::SpeakerIsInitialized() const { return 0; } -int32_t FakeAudioCaptureModule::MicrophoneIsAvailable(bool* available) { - // No microphone, just playing from file. Return success. - *available = true; - return 0; -} - int32_t FakeAudioCaptureModule::InitMicrophone() { // No microphone, just playing from file. Return success. return 0; diff --git a/app/webrtc/test/fakeaudiocapturemodule.h b/app/webrtc/test/fakeaudiocapturemodule.h index aec3e5e..79b72b6 100644 --- a/app/webrtc/test/fakeaudiocapturemodule.h +++ b/app/webrtc/test/fakeaudiocapturemodule.h @@ -76,133 +76,132 @@ class FakeAudioCaptureModule // Only functions called by PeerConnection are implemented, the rest do // nothing and return success. If a function is not expected to be called by // PeerConnection an assertion is triggered if it is in fact called. - virtual int32_t Version(char* version, - uint32_t& remaining_buffer_in_bytes, - uint32_t& position) const; - virtual int32_t TimeUntilNextProcess(); - virtual int32_t Process(); - virtual int32_t ChangeUniqueId(const int32_t id); + virtual int32_t TimeUntilNextProcess() OVERRIDE; + virtual int32_t Process() OVERRIDE; + virtual int32_t ChangeUniqueId(const int32_t id) OVERRIDE; - virtual int32_t ActiveAudioLayer(AudioLayer* audio_layer) const; + virtual int32_t ActiveAudioLayer(AudioLayer* audio_layer) const OVERRIDE; - virtual ErrorCode LastError() const; + virtual ErrorCode LastError() const OVERRIDE; virtual int32_t RegisterEventObserver( - webrtc::AudioDeviceObserver* event_callback); + webrtc::AudioDeviceObserver* event_callback) OVERRIDE; // Note: Calling this method from a callback may result in deadlock. - virtual int32_t RegisterAudioCallback(webrtc::AudioTransport* audio_callback); - - virtual int32_t Init(); - virtual int32_t Terminate(); - virtual bool Initialized() const; - - virtual int16_t PlayoutDevices(); - virtual int16_t RecordingDevices(); - virtual int32_t PlayoutDeviceName(uint16_t index, - char name[webrtc::kAdmMaxDeviceNameSize], - char guid[webrtc::kAdmMaxGuidSize]); - virtual int32_t RecordingDeviceName(uint16_t index, - char name[webrtc::kAdmMaxDeviceNameSize], - char guid[webrtc::kAdmMaxGuidSize]); - - virtual int32_t SetPlayoutDevice(uint16_t index); - virtual int32_t SetPlayoutDevice(WindowsDeviceType device); - virtual int32_t SetRecordingDevice(uint16_t index); - virtual int32_t SetRecordingDevice(WindowsDeviceType device); - - virtual int32_t PlayoutIsAvailable(bool* available); - virtual int32_t InitPlayout(); - virtual bool PlayoutIsInitialized() const; - virtual int32_t RecordingIsAvailable(bool* available); - virtual int32_t InitRecording(); - virtual bool RecordingIsInitialized() const; - - virtual int32_t StartPlayout(); - virtual int32_t StopPlayout(); - virtual bool Playing() const; - virtual int32_t StartRecording(); - virtual int32_t StopRecording(); - virtual bool Recording() const; - - virtual int32_t SetAGC(bool enable); - virtual bool AGC() const; + virtual int32_t RegisterAudioCallback( + webrtc::AudioTransport* audio_callback) OVERRIDE; + + virtual int32_t Init() OVERRIDE; + virtual int32_t Terminate() OVERRIDE; + virtual bool Initialized() const OVERRIDE; + + virtual int16_t PlayoutDevices() OVERRIDE; + virtual int16_t RecordingDevices() OVERRIDE; + virtual int32_t PlayoutDeviceName( + uint16_t index, + char name[webrtc::kAdmMaxDeviceNameSize], + char guid[webrtc::kAdmMaxGuidSize]) OVERRIDE; + virtual int32_t RecordingDeviceName( + uint16_t index, + char name[webrtc::kAdmMaxDeviceNameSize], + char guid[webrtc::kAdmMaxGuidSize]) OVERRIDE; + + virtual int32_t SetPlayoutDevice(uint16_t index) OVERRIDE; + virtual int32_t SetPlayoutDevice(WindowsDeviceType device) OVERRIDE; + virtual int32_t SetRecordingDevice(uint16_t index) OVERRIDE; + virtual int32_t SetRecordingDevice(WindowsDeviceType device) OVERRIDE; + + virtual int32_t PlayoutIsAvailable(bool* available) OVERRIDE; + virtual int32_t InitPlayout() OVERRIDE; + virtual bool PlayoutIsInitialized() const OVERRIDE; + virtual int32_t RecordingIsAvailable(bool* available) OVERRIDE; + virtual int32_t InitRecording() OVERRIDE; + virtual bool RecordingIsInitialized() const OVERRIDE; + + virtual int32_t StartPlayout() OVERRIDE; + virtual int32_t StopPlayout() OVERRIDE; + virtual bool Playing() const OVERRIDE; + virtual int32_t StartRecording() OVERRIDE; + virtual int32_t StopRecording() OVERRIDE; + virtual bool Recording() const OVERRIDE; + + virtual int32_t SetAGC(bool enable) OVERRIDE; + virtual bool AGC() const OVERRIDE; virtual int32_t SetWaveOutVolume(uint16_t volume_left, - uint16_t volume_right); + uint16_t volume_right) OVERRIDE; virtual int32_t WaveOutVolume(uint16_t* volume_left, - uint16_t* volume_right) const; - - virtual int32_t SpeakerIsAvailable(bool* available); - virtual int32_t InitSpeaker(); - virtual bool SpeakerIsInitialized() const; - virtual int32_t MicrophoneIsAvailable(bool* available); - virtual int32_t InitMicrophone(); - virtual bool MicrophoneIsInitialized() const; - - virtual int32_t SpeakerVolumeIsAvailable(bool* available); - virtual int32_t SetSpeakerVolume(uint32_t volume); - virtual int32_t SpeakerVolume(uint32_t* volume) const; - virtual int32_t MaxSpeakerVolume(uint32_t* max_volume) const; - virtual int32_t MinSpeakerVolume(uint32_t* min_volume) const; - virtual int32_t SpeakerVolumeStepSize(uint16_t* step_size) const; - - virtual int32_t MicrophoneVolumeIsAvailable(bool* available); - virtual int32_t SetMicrophoneVolume(uint32_t volume); - virtual int32_t MicrophoneVolume(uint32_t* volume) const; - virtual int32_t MaxMicrophoneVolume(uint32_t* max_volume) const; - - virtual int32_t MinMicrophoneVolume(uint32_t* min_volume) const; - virtual int32_t MicrophoneVolumeStepSize(uint16_t* step_size) const; - - virtual int32_t SpeakerMuteIsAvailable(bool* available); - virtual int32_t SetSpeakerMute(bool enable); - virtual int32_t SpeakerMute(bool* enabled) const; - - virtual int32_t MicrophoneMuteIsAvailable(bool* available); - virtual int32_t SetMicrophoneMute(bool enable); - virtual int32_t MicrophoneMute(bool* enabled) const; - - virtual int32_t MicrophoneBoostIsAvailable(bool* available); - virtual int32_t SetMicrophoneBoost(bool enable); - virtual int32_t MicrophoneBoost(bool* enabled) const; - - virtual int32_t StereoPlayoutIsAvailable(bool* available) const; - virtual int32_t SetStereoPlayout(bool enable); - virtual int32_t StereoPlayout(bool* enabled) const; - virtual int32_t StereoRecordingIsAvailable(bool* available) const; - virtual int32_t SetStereoRecording(bool enable); - virtual int32_t StereoRecording(bool* enabled) const; - virtual int32_t SetRecordingChannel(const ChannelType channel); - virtual int32_t RecordingChannel(ChannelType* channel) const; + uint16_t* volume_right) const OVERRIDE; + + virtual int32_t InitSpeaker() OVERRIDE; + virtual bool SpeakerIsInitialized() const OVERRIDE; + virtual int32_t InitMicrophone() OVERRIDE; + virtual bool MicrophoneIsInitialized() const OVERRIDE; + + virtual int32_t SpeakerVolumeIsAvailable(bool* available) OVERRIDE; + virtual int32_t SetSpeakerVolume(uint32_t volume) OVERRIDE; + virtual int32_t SpeakerVolume(uint32_t* volume) const OVERRIDE; + virtual int32_t MaxSpeakerVolume(uint32_t* max_volume) const OVERRIDE; + virtual int32_t MinSpeakerVolume(uint32_t* min_volume) const OVERRIDE; + virtual int32_t SpeakerVolumeStepSize(uint16_t* step_size) const OVERRIDE; + + virtual int32_t MicrophoneVolumeIsAvailable(bool* available) OVERRIDE; + virtual int32_t SetMicrophoneVolume(uint32_t volume) OVERRIDE; + virtual int32_t MicrophoneVolume(uint32_t* volume) const OVERRIDE; + virtual int32_t MaxMicrophoneVolume(uint32_t* max_volume) const OVERRIDE; + + virtual int32_t MinMicrophoneVolume(uint32_t* min_volume) const OVERRIDE; + virtual int32_t MicrophoneVolumeStepSize(uint16_t* step_size) const OVERRIDE; + + virtual int32_t SpeakerMuteIsAvailable(bool* available) OVERRIDE; + virtual int32_t SetSpeakerMute(bool enable) OVERRIDE; + virtual int32_t SpeakerMute(bool* enabled) const OVERRIDE; + + virtual int32_t MicrophoneMuteIsAvailable(bool* available) OVERRIDE; + virtual int32_t SetMicrophoneMute(bool enable) OVERRIDE; + virtual int32_t MicrophoneMute(bool* enabled) const OVERRIDE; + + virtual int32_t MicrophoneBoostIsAvailable(bool* available) OVERRIDE; + virtual int32_t SetMicrophoneBoost(bool enable) OVERRIDE; + virtual int32_t MicrophoneBoost(bool* enabled) const OVERRIDE; + + virtual int32_t StereoPlayoutIsAvailable(bool* available) const OVERRIDE; + virtual int32_t SetStereoPlayout(bool enable) OVERRIDE; + virtual int32_t StereoPlayout(bool* enabled) const OVERRIDE; + virtual int32_t StereoRecordingIsAvailable(bool* available) const OVERRIDE; + virtual int32_t SetStereoRecording(bool enable) OVERRIDE; + virtual int32_t StereoRecording(bool* enabled) const OVERRIDE; + virtual int32_t SetRecordingChannel(const ChannelType channel) OVERRIDE; + virtual int32_t RecordingChannel(ChannelType* channel) const OVERRIDE; virtual int32_t SetPlayoutBuffer(const BufferType type, - uint16_t size_ms = 0); + uint16_t size_ms = 0) OVERRIDE; virtual int32_t PlayoutBuffer(BufferType* type, - uint16_t* size_ms) const; - virtual int32_t PlayoutDelay(uint16_t* delay_ms) const; - virtual int32_t RecordingDelay(uint16_t* delay_ms) const; + uint16_t* size_ms) const OVERRIDE; + virtual int32_t PlayoutDelay(uint16_t* delay_ms) const OVERRIDE; + virtual int32_t RecordingDelay(uint16_t* delay_ms) const OVERRIDE; - virtual int32_t CPULoad(uint16_t* load) const; + virtual int32_t CPULoad(uint16_t* load) const OVERRIDE; virtual int32_t StartRawOutputFileRecording( - const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]); - virtual int32_t StopRawOutputFileRecording(); + const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]) OVERRIDE; + virtual int32_t StopRawOutputFileRecording() OVERRIDE; virtual int32_t StartRawInputFileRecording( - const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]); - virtual int32_t StopRawInputFileRecording(); - - virtual int32_t SetRecordingSampleRate(const uint32_t samples_per_sec); - virtual int32_t RecordingSampleRate(uint32_t* samples_per_sec) const; - virtual int32_t SetPlayoutSampleRate(const uint32_t samples_per_sec); - virtual int32_t PlayoutSampleRate(uint32_t* samples_per_sec) const; - - virtual int32_t ResetAudioDevice(); - virtual int32_t SetLoudspeakerStatus(bool enable); - virtual int32_t GetLoudspeakerStatus(bool* enabled) const; + const char pcm_file_name_utf8[webrtc::kAdmMaxFileNameSize]) OVERRIDE; + virtual int32_t StopRawInputFileRecording() OVERRIDE; + + virtual int32_t SetRecordingSampleRate( + const uint32_t samples_per_sec) OVERRIDE; + virtual int32_t RecordingSampleRate(uint32_t* samples_per_sec) const OVERRIDE; + virtual int32_t SetPlayoutSampleRate(const uint32_t samples_per_sec) OVERRIDE; + virtual int32_t PlayoutSampleRate(uint32_t* samples_per_sec) const OVERRIDE; + + virtual int32_t ResetAudioDevice() OVERRIDE; + virtual int32_t SetLoudspeakerStatus(bool enable) OVERRIDE; + virtual int32_t GetLoudspeakerStatus(bool* enabled) const OVERRIDE; // End of functions inherited from webrtc::AudioDeviceModule. // The following function is inherited from rtc::MessageHandler. - virtual void OnMessage(rtc::Message* msg); + virtual void OnMessage(rtc::Message* msg) OVERRIDE; protected: // The constructor is protected because the class needs to be created as a diff --git a/app/webrtc/test/fakeaudiocapturemodule_unittest.cc b/app/webrtc/test/fakeaudiocapturemodule_unittest.cc index 9e63c1c..ddacc38 100644 --- a/app/webrtc/test/fakeaudiocapturemodule_unittest.cc +++ b/app/webrtc/test/fakeaudiocapturemodule_unittest.cc @@ -145,11 +145,6 @@ TEST_F(FakeAdmTest, TestProccess) { TEST_F(FakeAdmTest, PlayoutTest) { EXPECT_EQ(0, fake_audio_capture_module_->RegisterAudioCallback(this)); - bool speaker_available = false; - EXPECT_EQ(0, fake_audio_capture_module_->SpeakerIsAvailable( - &speaker_available)); - EXPECT_TRUE(speaker_available); - bool stereo_available = false; EXPECT_EQ(0, fake_audio_capture_module_->StereoPlayoutIsAvailable( @@ -182,11 +177,6 @@ TEST_F(FakeAdmTest, PlayoutTest) { TEST_F(FakeAdmTest, RecordTest) { EXPECT_EQ(0, fake_audio_capture_module_->RegisterAudioCallback(this)); - bool microphone_available = false; - EXPECT_EQ(0, fake_audio_capture_module_->MicrophoneIsAvailable( - µphone_available)); - EXPECT_TRUE(microphone_available); - bool stereo_available = false; EXPECT_EQ(0, fake_audio_capture_module_->StereoRecordingIsAvailable( &stereo_available)); diff --git a/app/webrtc/test/mockpeerconnectionobservers.h b/app/webrtc/test/mockpeerconnectionobservers.h index 174b80b..0570d40 100644 --- a/app/webrtc/test/mockpeerconnectionobservers.h +++ b/app/webrtc/test/mockpeerconnectionobservers.h @@ -133,31 +133,37 @@ class MockStatsObserver : public webrtc::StatsObserver { size_t number_of_reports() const { return reports_.size(); } int AudioOutputLevel() { - return GetSsrcStatsValue( - webrtc::StatsReport::kStatsValueNameAudioOutputLevel); + return GetStatsValue(StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameAudioOutputLevel); } int AudioInputLevel() { - return GetSsrcStatsValue( - webrtc::StatsReport::kStatsValueNameAudioInputLevel); + return GetStatsValue(StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameAudioInputLevel); } int BytesReceived() { - return GetSsrcStatsValue( - webrtc::StatsReport::kStatsValueNameBytesReceived); + return GetStatsValue(StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameBytesReceived); } int BytesSent() { - return GetSsrcStatsValue(webrtc::StatsReport::kStatsValueNameBytesSent); + return GetStatsValue(StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsValueNameBytesSent); + } + + int AvailableReceiveBandwidth() { + return GetStatsValue(StatsReport::kStatsReportTypeBwe, + StatsReport::kStatsValueNameAvailableReceiveBandwidth); } private: - int GetSsrcStatsValue(StatsReport::StatsValueName name) { + int GetStatsValue(const std::string& type, StatsReport::StatsValueName name) { if (reports_.empty()) { return 0; } for (size_t i = 0; i < reports_.size(); ++i) { - if (reports_[i].type != StatsReport::kStatsReportTypeSsrc) + if (reports_[i].type != type) continue; webrtc::StatsReport::Values::const_iterator it = reports_[i].values.begin(); diff --git a/app/webrtc/test/peerconnectiontestwrapper.cc b/app/webrtc/test/peerconnectiontestwrapper.cc index 8a4f45c..24932b8 100644 --- a/app/webrtc/test/peerconnectiontestwrapper.cc +++ b/app/webrtc/test/peerconnectiontestwrapper.cc @@ -75,9 +75,8 @@ bool PeerConnectionTestWrapper::CreatePc( return false; } - audio_thread_.Start(); fake_audio_capture_module_ = FakeAudioCaptureModule::Create( - &audio_thread_); + rtc::Thread::Current()); if (fake_audio_capture_module_ == NULL) { return false; } diff --git a/app/webrtc/test/peerconnectiontestwrapper.h b/app/webrtc/test/peerconnectiontestwrapper.h index f3477ce..d4a0e4e 100644 --- a/app/webrtc/test/peerconnectiontestwrapper.h +++ b/app/webrtc/test/peerconnectiontestwrapper.h @@ -111,7 +111,6 @@ class PeerConnectionTestWrapper bool video, const webrtc::FakeConstraints& video_constraints); std::string name_; - rtc::Thread audio_thread_; rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> allocator_factory_; rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_; diff --git a/app/webrtc/videosource.cc b/app/webrtc/videosource.cc index 8770e6d..589341d 100644 --- a/app/webrtc/videosource.cc +++ b/app/webrtc/videosource.cc @@ -28,6 +28,7 @@ #include "talk/app/webrtc/videosource.h" #include <vector> +#include <cstdlib> #include "talk/app/webrtc/mediaconstraintsinterface.h" #include "talk/session/media/channelmanager.h" @@ -254,11 +255,15 @@ const cricket::VideoFormat& GetBestCaptureFormat( std::vector<cricket::VideoFormat>::const_iterator it = formats.begin(); std::vector<cricket::VideoFormat>::const_iterator best_it = formats.begin(); - int best_diff = abs(default_area - it->width* it->height); + int best_diff_area = std::abs(default_area - it->width * it->height); + int64 best_diff_interval = kDefaultFormat.interval; for (; it != formats.end(); ++it) { - int diff = abs(default_area - it->width* it->height); - if (diff < best_diff) { - best_diff = diff; + int diff_area = std::abs(default_area - it->width * it->height); + int64 diff_interval = std::abs(kDefaultFormat.interval - it->interval); + if (diff_area < best_diff_area || + (diff_area == best_diff_area && diff_interval < best_diff_interval)) { + best_diff_area = diff_area; + best_diff_interval = diff_interval; best_it = it; } } diff --git a/app/webrtc/webrtcsdp.cc b/app/webrtc/webrtcsdp.cc index 792a091..5887409 100644 --- a/app/webrtc/webrtcsdp.cc +++ b/app/webrtc/webrtcsdp.cc @@ -71,6 +71,7 @@ using cricket::kCodecParamUseInbandFec; using cricket::kCodecParamSctpProtocol; using cricket::kCodecParamSctpStreams; using cricket::kCodecParamMaxAverageBitrate; +using cricket::kCodecParamMaxPlaybackRate; using cricket::kCodecParamAssociatedPayloadType; using cricket::kWildcardPayloadType; using cricket::MediaContentDescription; @@ -155,6 +156,9 @@ static const char kAttributeRecvOnly[] = "recvonly"; static const char kAttributeRtcpFb[] = "rtcp-fb"; static const char kAttributeSendRecv[] = "sendrecv"; static const char kAttributeInactive[] = "inactive"; +// draft-ietf-mmusic-sctp-sdp-07 +// a=sctp-port +static const char kAttributeSctpPort[] = "sctp-port"; // Experimental flags static const char kAttributeXGoogleFlag[] = "x-google-flag"; @@ -1100,6 +1104,26 @@ bool ParseIceOptions(const std::string& line, return true; } +bool ParseSctpPort(const std::string& line, + int* sctp_port, + SdpParseError* error) { + // draft-ietf-mmusic-sctp-sdp-07 + // a=sctp-port + std::vector<std::string> fields; + rtc::split(line.substr(kLinePrefixLength), + kSdpDelimiterSpace, &fields); + const size_t expected_min_fields = 2; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + if (!rtc::FromString(fields[1], sctp_port)) { + return ParseFailed(line, + "Invalid sctp port value.", + error); + } + return true; +} + bool ParseExtmap(const std::string& line, RtpHeaderExtension* extmap, SdpParseError* error) { // RFC 5285 @@ -1511,7 +1535,8 @@ bool IsFmtpParam(const std::string& name) { kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamStartBitrate, kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization, kCodecParamSctpProtocol, kCodecParamSctpStreams, - kCodecParamMaxAverageBitrate, kCodecParamAssociatedPayloadType + kCodecParamMaxAverageBitrate, kCodecParamMaxPlaybackRate, + kCodecParamAssociatedPayloadType }; for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) { if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) { @@ -1563,6 +1588,24 @@ void AddRtcpFbLines(const T& codec, std::string* message) { } } +bool AddSctpDataCodec(DataContentDescription* media_desc, + int sctp_port) { + if (media_desc->HasCodec(cricket::kGoogleSctpDataCodecId)) { + return ParseFailed("", + "Can't have multiple sctp port attributes.", + NULL); + } + // Add the SCTP Port number as a pseudo-codec "port" parameter + cricket::DataCodec codec_port( + cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, + 0); + codec_port.SetParam(cricket::kCodecParamPort, sctp_port); + LOG(INFO) << "AddSctpDataCodec: Got SCTP Port Number " + << sctp_port; + media_desc->AddCodec(codec_port); + return true; +} + bool GetMinValue(const std::vector<int>& values, int* value) { if (values.empty()) { return false; @@ -2129,18 +2172,20 @@ bool ParseMediaDescription(const std::string& message, // <fmt> std::vector<int> codec_preference; - for (size_t j = 3 ; j < fields.size(); ++j) { - // TODO(wu): Remove when below bug is fixed. - // https://bugzilla.mozilla.org/show_bug.cgi?id=996329 - if (fields[j] == "" && j == fields.size() - 1) { - continue; - } + if (!is_sctp) { + for (size_t j = 3 ; j < fields.size(); ++j) { + // TODO(wu): Remove when below bug is fixed. + // https://bugzilla.mozilla.org/show_bug.cgi?id=996329 + if (fields[j] == "" && j == fields.size() - 1) { + continue; + } - int pl = 0; - if (!GetValueFromString(line, fields[j], &pl, error)) { - return false; + int pl = 0; + if (!GetValueFromString(line, fields[j], &pl, error)) { + return false; + } + codec_preference.push_back(pl); } - codec_preference.push_back(pl); } // Make a temporary TransportDescription based on |session_td|. @@ -2167,26 +2212,20 @@ bool ParseMediaDescription(const std::string& message, codec_preference, pos, &content_name, &transport, candidates, error)); } else if (HasAttribute(line, kMediaTypeData)) { - DataContentDescription* desc = + DataContentDescription* data_desc = ParseContentDescription<DataContentDescription>( message, cricket::MEDIA_TYPE_DATA, mline_index, protocol, codec_preference, pos, &content_name, &transport, candidates, error); + content.reset(data_desc); - if (desc && protocol == cricket::kMediaProtocolDtlsSctp) { - // Add the SCTP Port number as a pseudo-codec "port" parameter - cricket::DataCodec codec_port( - cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, - 0); - codec_port.SetParam(cricket::kCodecParamPort, fields[3]); - LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number " - << fields[3]; - ASSERT(!desc->HasCodec(cricket::kGoogleSctpDataCodecId)); - desc->AddCodec(codec_port); + int p; + if (data_desc && protocol == cricket::kMediaProtocolDtlsSctp && + rtc::FromString(fields[3], &p)) { + if (!AddSctpDataCodec(data_desc, p)) + return false; } - content.reset(desc); - // We should always use the default bandwidth for RTP-based data // channels. Don't allow SDP to set the bandwidth, because that // would give JS the opportunity to "break the Internet". @@ -2518,6 +2557,15 @@ bool ParseContent(const std::string& message, if (!ParseDtlsSetup(line, &(transport->connection_role), error)) { return false; } + } else if (HasAttribute(line, kAttributeSctpPort)) { + int sctp_port; + if (!ParseSctpPort(line, &sctp_port, error)) { + return false; + } + if (!AddSctpDataCodec(static_cast<DataContentDescription*>(media_desc), + sctp_port)) { + return false; + } } else if (is_rtp) { // // RTP specific attrubtes diff --git a/app/webrtc/webrtcsdp_unittest.cc b/app/webrtc/webrtcsdp_unittest.cc index 6a22e38..560d5da 100644 --- a/app/webrtc/webrtcsdp_unittest.cc +++ b/app/webrtc/webrtcsdp_unittest.cc @@ -284,6 +284,16 @@ static const char kSdpSctpDataChannelString[] = "a=mid:data_content_name\r\n" "a=sctpmap:5000 webrtc-datachannel 1024\r\n"; +// draft-ietf-mmusic-sctp-sdp-07 +static const char kSdpSctpDataChannelStringWithSctpPort[] = + "m=application 1 DTLS/SCTP webrtc-datachannel\r\n" + "a=fmtp:webrtc-datachannel max-message-size=100000\r\n" + "a=sctp-port 5000\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=ice-ufrag:ufrag_data\r\n" + "a=ice-pwd:pwd_data\r\n" + "a=mid:data_content_name\r\n"; + static const char kSdpSctpDataChannelWithCandidatesString[] = "m=application 2345 DTLS/SCTP 5000\r\n" "c=IN IP4 74.125.127.126\r\n" @@ -2023,6 +2033,36 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannels) { EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); } +TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelsWithSctpPort) { + AddSctpDataChannel(); + JsepSessionDescription jdesc(kDummyString); + ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + + std::string sdp_with_data = kSdpString; + sdp_with_data.append(kSdpSctpDataChannelStringWithSctpPort); + JsepSessionDescription jdesc_output(kDummyString); + + EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output)); + EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); +} + +// Test to check the behaviour if sctp-port is specified +// on the m= line and in a=sctp-port. +TEST_F(WebRtcSdpTest, DeserializeSdpWithMultiSctpPort) { + AddSctpDataChannel(); + JsepSessionDescription jdesc(kDummyString); + ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion)); + + std::string sdp_with_data = kSdpString; + // Append m= attributes + sdp_with_data.append(kSdpSctpDataChannelString); + // Append a=sctp-port attribute + sdp_with_data.append("a=sctp-port 5000\r\n"); + JsepSessionDescription jdesc_output(kDummyString); + + EXPECT_FALSE(SdpDeserialize(sdp_with_data, &jdesc_output)); +} + // For crbug/344475. TEST_F(WebRtcSdpTest, DeserializeSdpWithCorruptedSctpDataChannels) { std::string sdp_with_data = kSdpString; @@ -2071,6 +2111,19 @@ TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) { EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output)); EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); + + // We need to test the deserialized JsepSessionDescription from + // kSdpSctpDataChannelStringWithSctpPort for + // draft-ietf-mmusic-sctp-sdp-07 + // a=sctp-port + sdp_with_data = kSdpString; + sdp_with_data.append(kSdpSctpDataChannelStringWithSctpPort); + rtc::replace_substrs(default_portstr, strlen(default_portstr), + unusual_portstr, strlen(unusual_portstr), + &sdp_with_data); + + EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output)); + EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output)); } TEST_F(WebRtcSdpTest, DeserializeSdpWithRtpDataChannelsAndBandwidth) { diff --git a/app/webrtc/webrtcsession.cc b/app/webrtc/webrtcsession.cc index 6d57afb..13e2128 100644 --- a/app/webrtc/webrtcsession.cc +++ b/app/webrtc/webrtcsession.cc @@ -224,30 +224,28 @@ static bool GetTrackIdBySsrc(const SessionDescription* session_description, cricket::StreamParams stream_out; const cricket::ContentInfo* audio_info = cricket::GetFirstAudioContent(session_description); - if (!audio_info) { - return false; - } - const cricket::MediaContentDescription* audio_content = - static_cast<const cricket::MediaContentDescription*>( - audio_info->description); - - if (cricket::GetStreamBySsrc(audio_content->streams(), ssrc, &stream_out)) { - *track_id = stream_out.id; - return true; + if (audio_info) { + const cricket::MediaContentDescription* audio_content = + static_cast<const cricket::MediaContentDescription*>( + audio_info->description); + + if (cricket::GetStreamBySsrc(audio_content->streams(), ssrc, &stream_out)) { + *track_id = stream_out.id; + return true; + } } const cricket::ContentInfo* video_info = cricket::GetFirstVideoContent(session_description); - if (!video_info) { - return false; - } - const cricket::MediaContentDescription* video_content = - static_cast<const cricket::MediaContentDescription*>( - video_info->description); - - if (cricket::GetStreamBySsrc(video_content->streams(), ssrc, &stream_out)) { - *track_id = stream_out.id; - return true; + if (video_info) { + const cricket::MediaContentDescription* video_content = + static_cast<const cricket::MediaContentDescription*>( + video_info->description); + + if (cricket::GetStreamBySsrc(video_content->streams(), ssrc, &stream_out)) { + *track_id = stream_out.id; + return true; + } } return false; } @@ -389,6 +387,22 @@ static void SetOptionFromOptionalConstraint( } } +uint32 ConvertIceTransportTypeToCandidateFilter( + PeerConnectionInterface::IceTransportsType type) { + switch (type) { + case PeerConnectionInterface::kNone: + return cricket::CF_NONE; + case PeerConnectionInterface::kRelay: + return cricket::CF_RELAY; + case PeerConnectionInterface::kNoHost: + return (cricket::CF_ALL & ~cricket::CF_HOST); + case PeerConnectionInterface::kAll: + return cricket::CF_ALL; + default: ASSERT(false); + } + return cricket::CF_NONE; +} + // Help class used to remember if a a remote peer has requested ice restart by // by sending a description with new ice ufrag and password. class IceRestartAnswerLatch { @@ -471,14 +485,16 @@ WebRtcSession::WebRtcSession( } WebRtcSession::~WebRtcSession() { - if (voice_channel_.get()) { - SignalVoiceChannelDestroyed(); - channel_manager_->DestroyVoiceChannel(voice_channel_.release()); - } + // Destroy video_channel_ first since it may have a pointer to the + // voice_channel_. if (video_channel_.get()) { SignalVideoChannelDestroyed(); channel_manager_->DestroyVideoChannel(video_channel_.release()); } + if (voice_channel_.get()) { + SignalVoiceChannelDestroyed(); + channel_manager_->DestroyVoiceChannel(voice_channel_.release()); + } if (data_channel_.get()) { SignalDataChannelDestroyed(); channel_manager_->DestroyDataChannel(data_channel_.release()); @@ -610,6 +626,10 @@ bool WebRtcSession::Initialize( cricket::VideoOptions::HIGH); } + SetOptionFromOptionalConstraint(constraints, + MediaConstraintsInterface::kCombinedAudioVideoBwe, + &audio_options_.combined_audio_video_bwe); + const cricket::VideoCodec default_codec( JsepSessionDescription::kDefaultVideoCodecId, JsepSessionDescription::kDefaultVideoCodecName, @@ -636,7 +656,8 @@ bool WebRtcSession::Initialize( if (options.disable_encryption) { webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED); } - + port_allocator()->set_candidate_filter( + ConvertIceTransportTypeToCandidateFilter(ice_transport)); return true; } @@ -742,6 +763,7 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc, if (!UpdateSessionState(action, cricket::CS_LOCAL, err_desc)) { return false; } + // Kick starting the ice candidates allocation. StartCandidatesAllocation(); @@ -903,8 +925,10 @@ bool WebRtcSession::ProcessIceMessage(const IceCandidateInterface* candidate) { return UseCandidate(candidate); } -bool WebRtcSession::UpdateIce(PeerConnectionInterface::IceTransportsType type) { - return false; +bool WebRtcSession::SetIceTransports( + PeerConnectionInterface::IceTransportsType type) { + return port_allocator()->set_candidate_filter( + ConvertIceTransportTypeToCandidateFilter(type)); } bool WebRtcSession::GetLocalTrackIdBySsrc(uint32 ssrc, std::string* track_id) { @@ -1425,16 +1449,8 @@ bool WebRtcSession::UseCandidate( void WebRtcSession::RemoveUnusedChannelsAndTransports( const SessionDescription* desc) { - const cricket::ContentInfo* voice_info = - cricket::GetFirstAudioContent(desc); - if ((!voice_info || voice_info->rejected) && voice_channel_) { - mediastream_signaling_->OnAudioChannelClose(); - SignalVoiceChannelDestroyed(); - const std::string content_name = voice_channel_->content_name(); - channel_manager_->DestroyVoiceChannel(voice_channel_.release()); - DestroyTransportProxy(content_name); - } - + // Destroy video_channel_ first since it may have a pointer to the + // voice_channel_. const cricket::ContentInfo* video_info = cricket::GetFirstVideoContent(desc); if ((!video_info || video_info->rejected) && video_channel_) { @@ -1445,6 +1461,16 @@ void WebRtcSession::RemoveUnusedChannelsAndTransports( DestroyTransportProxy(content_name); } + const cricket::ContentInfo* voice_info = + cricket::GetFirstAudioContent(desc); + if ((!voice_info || voice_info->rejected) && voice_channel_) { + mediastream_signaling_->OnAudioChannelClose(); + SignalVoiceChannelDestroyed(); + const std::string content_name = voice_channel_->content_name(); + channel_manager_->DestroyVoiceChannel(voice_channel_.release()); + DestroyTransportProxy(content_name); + } + const cricket::ContentInfo* data_info = cricket::GetFirstDataContent(desc); if ((!data_info || data_info->rejected) && data_channel_) { diff --git a/app/webrtc/webrtcsession.h b/app/webrtc/webrtcsession.h index d1e5645..86ae435 100644 --- a/app/webrtc/webrtcsession.h +++ b/app/webrtc/webrtcsession.h @@ -160,7 +160,7 @@ class WebRtcSession : public cricket::BaseSession, std::string* err_desc); bool ProcessIceMessage(const IceCandidateInterface* ice_candidate); - bool UpdateIce(PeerConnectionInterface::IceTransportsType type); + bool SetIceTransports(PeerConnectionInterface::IceTransportsType type); const SessionDescriptionInterface* local_description() const { return local_desc_.get(); diff --git a/app/webrtc/webrtcsession_unittest.cc b/app/webrtc/webrtcsession_unittest.cc index 206f320..7aa87fb 100644 --- a/app/webrtc/webrtcsession_unittest.cc +++ b/app/webrtc/webrtcsession_unittest.cc @@ -108,6 +108,8 @@ static const char kClientAddrHost2[] = "22.22.22.22"; static const char kStunAddrHost[] = "99.99.99.1"; static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478); static const SocketAddress kTurnUdpExtAddr("99.99.99.6", 0); +static const char kTurnUsername[] = "test"; +static const char kTurnPassword[] = "test"; static const char kSessionVersion[] = "1"; @@ -510,7 +512,7 @@ class WebRtcSessionTest : public testing::Test { } void VerifyAnswerFromNonCryptoOffer() { - // Create a SDP without Crypto. + // Create an SDP without Crypto. cricket::MediaSessionOptions options; options.has_video = true; JsepSessionDescription* offer( @@ -1083,6 +1085,18 @@ class WebRtcSessionTest : public testing::Test { } } + void ConfigureAllocatorWithTurn() { + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + allocator_->AddRelay(relay_server); + allocator_->set_step_delay(cricket::kMinimumStepDelay); + allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_ENABLE_BUNDLE); + } + cricket::FakeMediaEngine* media_engine_; cricket::FakeDataEngine* data_engine_; cricket::FakeDeviceManager* device_manager_; @@ -1162,6 +1176,53 @@ TEST_F(WebRtcSessionTest, TestStunError) { EXPECT_EQ(6u, observer_.mline_1_candidates_.size()); } +// Test session delivers no candidates gathered when constraint set to "none". +TEST_F(WebRtcSessionTest, TestIceTransportsNone) { + AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); + SetIceTransportType(PeerConnectionInterface::kNone); + Init(NULL); + mediastream_signaling_.SendAudioVideoStream1(); + InitiateCall(); + EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); + EXPECT_EQ(0u, observer_.mline_0_candidates_.size()); + EXPECT_EQ(0u, observer_.mline_1_candidates_.size()); +} + +// Test session delivers only relay candidates gathered when constaint set to +// "relay". +TEST_F(WebRtcSessionTest, TestIceTransportsRelay) { + AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); + ConfigureAllocatorWithTurn(); + SetIceTransportType(PeerConnectionInterface::kRelay); + Init(NULL); + mediastream_signaling_.SendAudioVideoStream1(); + InitiateCall(); + EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); + EXPECT_EQ(2u, observer_.mline_0_candidates_.size()); + EXPECT_EQ(2u, observer_.mline_1_candidates_.size()); + for (size_t i = 0; i < observer_.mline_0_candidates_.size(); ++i) { + EXPECT_EQ(cricket::RELAY_PORT_TYPE, + observer_.mline_0_candidates_[i].type()); + } + for (size_t i = 0; i < observer_.mline_1_candidates_.size(); ++i) { + EXPECT_EQ(cricket::RELAY_PORT_TYPE, + observer_.mline_1_candidates_[i].type()); + } +} + +// Test session delivers all candidates gathered when constaint set to "all". +TEST_F(WebRtcSessionTest, TestIceTransportsAll) { + AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); + SetIceTransportType(PeerConnectionInterface::kAll); + Init(NULL); + mediastream_signaling_.SendAudioVideoStream1(); + InitiateCall(); + EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); + // Host + STUN. By default allocator is disabled to gather relay candidates. + EXPECT_EQ(4u, observer_.mline_0_candidates_.size()); + EXPECT_EQ(4u, observer_.mline_1_candidates_.size()); +} + TEST_F(WebRtcSessionTest, SetSdpFailedOnInvalidSdp) { Init(NULL); SessionDescriptionInterface* offer = NULL; @@ -1211,14 +1272,13 @@ TEST_F(WebRtcSessionTest, TestCreateSdesOfferReceiveSdesAnswer) { rtc::FromString<uint64>(offer->session_version())); SetLocalDescriptionWithoutError(offer); + EXPECT_EQ(0u, video_channel_->send_streams().size()); + EXPECT_EQ(0u, voice_channel_->send_streams().size()); mediastream_signaling_.SendAudioVideoStream2(); answer = CreateRemoteAnswer(session_->local_description()); SetRemoteDescriptionWithoutError(answer); - EXPECT_EQ(0u, video_channel_->send_streams().size()); - EXPECT_EQ(0u, voice_channel_->send_streams().size()); - // Make sure the receive streams have not changed. ASSERT_EQ(1u, video_channel_->recv_streams().size()); EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id); @@ -1992,13 +2052,21 @@ TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) { const cricket::ContentInfo* content = cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); + content = cricket::GetFirstVideoContent(offer->description()); EXPECT_TRUE(content != NULL); - // TODO(perkj): Should the direction be set to SEND_ONLY if - // The constraints is set to not receive audio or video but a track is added? + // Sets constraints to false and verifies that audio/video contents are + // removed. + options.offer_to_receive_audio = 0; + options.offer_to_receive_video = 0; + offer.reset(CreateOffer(options)); + + content = cricket::GetFirstAudioContent(offer->description()); + EXPECT_TRUE(content == NULL); + content = cricket::GetFirstVideoContent(offer->description()); + EXPECT_TRUE(content == NULL); } // Test that an answer can not be created if the last remote description is not @@ -2037,8 +2105,7 @@ TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { Init(NULL); // Create a remote offer with audio only. cricket::MediaSessionOptions options; - options.has_audio = true; - options.has_video = false; + rtc::scoped_ptr<JsepSessionDescription> offer( CreateRemoteOffer(options)); ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL); @@ -2174,7 +2241,6 @@ TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { SessionDescriptionInterface* offer = CreateOffer(); cricket::MediaSessionOptions options; - options.has_video = false; SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options); // SetLocalDescription and SetRemoteDescriptions takes ownership of offer @@ -2887,7 +2953,6 @@ TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) { TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { Init(NULL); cricket::MediaSessionOptions options; - options.has_audio = true; options.has_video = true; rtc::scoped_ptr<JsepSessionDescription> offer( CreateRemoteOffer(options)); @@ -2919,7 +2984,6 @@ TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) { Init(NULL); cricket::MediaSessionOptions options; - options.has_audio = true; options.has_video = true; rtc::scoped_ptr<JsepSessionDescription> offer( CreateRemoteOffer(options)); @@ -2985,7 +3049,6 @@ TEST_F(WebRtcSessionTest, TestIceStatesBundle) { TEST_F(WebRtcSessionTest, SetSdpFailedOnSessionError) { Init(NULL); cricket::MediaSessionOptions options; - options.has_audio = true; options.has_video = true; cricket::BaseSession::Error error_code = cricket::BaseSession::ERROR_CONTENT; @@ -3311,6 +3374,26 @@ TEST_F(WebRtcSessionTest, TestNumUnsignalledRecvStreamsConstraint) { SetAndVerifyNumUnsignalledRecvStreams(-1, 0); } +TEST_F(WebRtcSessionTest, TestCombinedAudioVideoBweConstraint) { + constraints_.reset(new FakeConstraints()); + constraints_->AddOptional( + webrtc::MediaConstraintsInterface::kCombinedAudioVideoBwe, + true); + Init(NULL); + mediastream_signaling_.SendAudioVideoStream1(); + SessionDescriptionInterface* offer = CreateOffer(); + + SetLocalDescriptionWithoutError(offer); + + voice_channel_ = media_engine_->GetVoiceChannel(0); + + ASSERT_TRUE(voice_channel_ != NULL); + cricket::AudioOptions audio_options; + EXPECT_TRUE(voice_channel_->GetOptions(&audio_options)); + EXPECT_TRUE( + audio_options.combined_audio_video_bwe.GetWithDefaultIfUnset(false)); +} + // Tests that we can renegotiate new media content with ICE candidates in the // new remote SDP. TEST_F(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesInSdp) { diff --git a/build/common.gypi b/build/common.gypi index 57b21af..7ee4224 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -91,6 +91,7 @@ # LateBindingSymbolTable::TableInfo from # latebindingsymboltable.cc.def and remove below flag. '-Wno-address-of-array-temporary', + '-Wthread-safety', ], }], ], diff --git a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java index 213da7b..468ce22 100644 --- a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java +++ b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java @@ -77,7 +77,6 @@ import java.util.regex.Pattern; public class AppRTCDemoActivity extends Activity implements AppRTCClient.IceServersObserver { private static final String TAG = "AppRTCDemoActivity"; - private static boolean factoryStaticInitialized; private PeerConnectionFactory factory; private VideoSource videoSource; private boolean videoSourceStopped; @@ -133,13 +132,6 @@ public class AppRTCDemoActivity extends Activity hudView.setVisibility(View.INVISIBLE); addContentView(hudView, hudLayout); - if (!factoryStaticInitialized) { - abortUnless(PeerConnectionFactory.initializeAndroidGlobals( - this, true, true), - "Failed to initializeAndroidGlobals"); - factoryStaticInitialized = true; - } - AudioManager audioManager = ((AudioManager) getSystemService(AUDIO_SERVICE)); // TODO(fischman): figure out how to do this Right(tm) and remove the @@ -282,6 +274,9 @@ public class AppRTCDemoActivity extends Activity @Override public void onIceServers(List<PeerConnection.IceServer> iceServers) { + abortUnless(PeerConnectionFactory.initializeAndroidGlobals( + this, true, true, VideoRendererGui.getEGLContext()), + "Failed to initializeAndroidGlobals"); factory = new PeerConnectionFactory(); MediaConstraints pcConstraints = appRtcClient.pcConstraints(); diff --git a/examples/call/callclient.cc b/examples/call/callclient.cc index 2c8a6bc..b31e24a 100644 --- a/examples/call/callclient.cc +++ b/examples/call/callclient.cc @@ -889,10 +889,8 @@ bool CallClient::PlaceCall(const std::string& name, AddSession(call_->InitiateSession(jid, media_client_->jid(), options)); } media_client_->SetFocus(call_); - if (call_->has_video() && render_) { - if (!options.is_muc) { - call_->SetLocalRenderer(local_renderer_); - } + if (call_->has_video() && render_ && !options.is_muc) { + // TODO(pthatcher): Hookup local_render_ to the local capturer. } if (options.is_muc) { const std::string& nick = mucs_[jid]->local_jid().resource(); @@ -1086,7 +1084,7 @@ void CallClient::Accept(const cricket::CallOptions& options) { call_->AcceptSession(session, options); media_client_->SetFocus(call_); if (call_->has_video() && render_) { - call_->SetLocalRenderer(local_renderer_); + // TODO(pthatcher): Hookup local_render_ to the local capturer. RenderAllStreams(call_, session, true); } SetupAcceptedCall(); diff --git a/examples/objc/AppRTCDemo/ios/APPRTCViewController.m b/examples/objc/AppRTCDemo/ios/APPRTCViewController.m index a4a0bd3..8042762 100644 --- a/examples/objc/AppRTCDemo/ios/APPRTCViewController.m +++ b/examples/objc/AppRTCDemo/ios/APPRTCViewController.m @@ -63,6 +63,18 @@ static CGFloat const kLocalViewPadding = 20; - (void)viewDidLoad { [super viewDidLoad]; + + self.remoteVideoView = + [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; + self.remoteVideoView.delegate = self; + self.remoteVideoView.transform = CGAffineTransformMakeScale(-1, 1); + [self.blackView addSubview:self.remoteVideoView]; + + self.localVideoView = + [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; + self.localVideoView.delegate = self; + [self.blackView addSubview:self.localVideoView]; + self.statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation; self.roomInput.delegate = self; @@ -181,25 +193,13 @@ static CGFloat const kLocalViewPadding = 20; self.instructionsView.hidden = NO; self.logView.hidden = YES; self.logView.text = nil; + self.localVideoView.videoTrack = nil; + self.remoteVideoView.videoTrack = nil; self.blackView.hidden = YES; - [self.remoteVideoView removeFromSuperview]; - self.remoteVideoView = nil; - [self.localVideoView removeFromSuperview]; - self.localVideoView = nil; } - (void)setupCaptureSession { self.blackView.hidden = NO; - self.remoteVideoView = - [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; - self.remoteVideoView.delegate = self; - self.remoteVideoView.transform = CGAffineTransformMakeScale(-1, 1); - [self.blackView addSubview:self.remoteVideoView]; - - self.localVideoView = - [[RTCEAGLVideoView alloc] initWithFrame:self.blackView.bounds]; - self.localVideoView.delegate = self; - [self.blackView addSubview:self.localVideoView]; [self updateVideoViewLayout]; } diff --git a/examples/peerconnection/server/utils.h b/examples/peerconnection/server/utils.h index d05a2c3..5320d5a 100644 --- a/examples/peerconnection/server/utils.h +++ b/examples/peerconnection/server/utils.h @@ -29,18 +29,7 @@ #define TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ #pragma once -#ifndef assert -#ifndef WIN32 #include <assert.h> -#else -#ifndef NDEBUG -#define assert(expr) ((void)((expr) ? true : __debugbreak())) -#else -#define assert(expr) ((void)0) -#endif // NDEBUG -#endif // WIN32 -#endif // assert - #include <string> #ifndef ARRAYSIZE diff --git a/libjingle.gyp b/libjingle.gyp index f4f9bf9..335a788 100755 --- a/libjingle.gyp +++ b/libjingle.gyp @@ -303,27 +303,13 @@ 'dependencies': [ '<(DEPTH)/third_party/expat/expat.gyp:expat', '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', - '<(webrtc_root)/base/base.gyp:webrtc_base', + '<(webrtc_root)/base/base.gyp:rtc_base', ], 'export_dependent_settings': [ '<(DEPTH)/third_party/expat/expat.gyp:expat', '<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp', ], 'sources': [ - 'xmllite/qname.cc', - 'xmllite/qname.h', - 'xmllite/xmlbuilder.cc', - 'xmllite/xmlbuilder.h', - 'xmllite/xmlconstants.cc', - 'xmllite/xmlconstants.h', - 'xmllite/xmlelement.cc', - 'xmllite/xmlelement.h', - 'xmllite/xmlnsstack.cc', - 'xmllite/xmlnsstack.h', - 'xmllite/xmlparser.cc', - 'xmllite/xmlparser.h', - 'xmllite/xmlprinter.cc', - 'xmllite/xmlprinter.h', 'xmpp/asyncsocket.h', 'xmpp/chatroommodule.h', 'xmpp/chatroommoduleimpl.cc', @@ -400,48 +386,6 @@ ], }, # target libjingle { - 'target_name': 'libjingle_sound', - 'type': 'static_library', - 'dependencies': [ - 'libjingle', - ], - 'sources': [ - 'sound/automaticallychosensoundsystem.h', - 'sound/nullsoundsystem.cc', - 'sound/nullsoundsystem.h', - 'sound/nullsoundsystemfactory.cc', - 'sound/nullsoundsystemfactory.h', - 'sound/platformsoundsystem.cc', - 'sound/platformsoundsystem.h', - 'sound/platformsoundsystemfactory.cc', - 'sound/platformsoundsystemfactory.h', - 'sound/sounddevicelocator.h', - 'sound/soundinputstreaminterface.h', - 'sound/soundoutputstreaminterface.h', - 'sound/soundsystemfactory.h', - 'sound/soundsysteminterface.cc', - 'sound/soundsysteminterface.h', - 'sound/soundsystemproxy.cc', - 'sound/soundsystemproxy.h', - ], - 'conditions': [ - ['OS=="linux"', { - 'sources': [ - 'sound/alsasoundsystem.cc', - 'sound/alsasoundsystem.h', - 'sound/alsasymboltable.cc', - 'sound/alsasymboltable.h', - 'sound/linuxsoundsystem.cc', - 'sound/linuxsoundsystem.h', - 'sound/pulseaudiosoundsystem.cc', - 'sound/pulseaudiosoundsystem.h', - 'sound/pulseaudiosymboltable.cc', - 'sound/pulseaudiosymboltable.h', - ], - }], - ], - }, # target libjingle_sound - { 'target_name': 'libjingle_media', 'type': 'static_library', 'include_dirs': [ @@ -452,14 +396,14 @@ 'dependencies': [ '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', '<(DEPTH)/third_party/usrsctp/usrsctp.gyp:usrsctplib', - '<(webrtc_root)/modules/modules.gyp:video_capture_module', '<(webrtc_root)/modules/modules.gyp:video_render_module', '<(webrtc_root)/webrtc.gyp:webrtc', '<(webrtc_root)/voice_engine/voice_engine.gyp:voice_engine', + '<(webrtc_root)/sound/sound.gyp:rtc_sound', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:field_trial_default', + '<(webrtc_root)/libjingle/xmllite/xmllite.gyp:rtc_xmllite', 'libjingle', - 'libjingle_sound', ], 'direct_dependent_settings': { 'include_dirs': [ @@ -485,8 +429,6 @@ 'media/base/filemediaengine.cc', 'media/base/filemediaengine.h', 'media/base/hybriddataengine.h', - 'media/base/hybridvideoengine.cc', - 'media/base/hybridvideoengine.h', 'media/base/mediachannel.h', 'media/base/mediacommon.h', 'media/base/mediaengine.cc', @@ -556,6 +498,17 @@ 'media/webrtc/webrtcvoiceengine.h', ], 'conditions': [ + ['build_with_chromium==1', { + 'dependencies': [ + '<(webrtc_root)/modules/modules.gyp:video_capture_module_impl', + '<(webrtc_root)/modules/modules.gyp:video_render_module_impl', + ], + }, { + 'dependencies': [ + '<(webrtc_root)/modules/modules.gyp:video_capture_module_internal_impl', + '<(webrtc_root)/modules/modules.gyp:video_render_module_internal_impl', + ], + }], ['OS=="linux"', { 'sources': [ 'media/devices/gtkvideorenderer.cc', @@ -631,6 +584,7 @@ 'link_settings': { 'xcode_settings': { 'OTHER_LDFLAGS': [ + '-weak_framework AVFoundation', '-framework Cocoa', '-framework CoreAudio', '-framework CoreVideo', diff --git a/libjingle_tests.gyp b/libjingle_tests.gyp index 76f22ea..44adec3 100755 --- a/libjingle_tests.gyp +++ b/libjingle_tests.gyp @@ -33,7 +33,7 @@ 'type': 'static_library', 'dependencies': [ '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', '<@(libjingle_tests_additional_deps)', ], 'direct_dependent_settings': { @@ -74,18 +74,13 @@ 'type': 'executable', 'includes': [ 'build/ios_tests.gypi', ], 'dependencies': [ - '<(webrtc_root)/base/base.gyp:webrtc_base', - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base.gyp:rtc_base', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', + '<(webrtc_root)/libjingle/xmllite/xmllite.gyp:rtc_xmllite', 'libjingle.gyp:libjingle', 'libjingle_unittest_main', ], 'sources': [ - 'xmllite/qname_unittest.cc', - 'xmllite/xmlbuilder_unittest.cc', - 'xmllite/xmlelement_unittest.cc', - 'xmllite/xmlnsstack_unittest.cc', - 'xmllite/xmlparser_unittest.cc', - 'xmllite/xmlprinter_unittest.cc', 'xmpp/fakexmppclient.h', 'xmpp/hangoutpubsubclient_unittest.cc', 'xmpp/jid_unittest.cc', @@ -104,22 +99,10 @@ ], # sources }, # target libjingle_unittest { - 'target_name': 'libjingle_sound_unittest', - 'type': 'executable', - 'dependencies': [ - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', - 'libjingle.gyp:libjingle_sound', - 'libjingle_unittest_main', - ], - 'sources': [ - 'sound/automaticallychosensoundsystem_unittest.cc', - ], - }, # target libjingle_sound_unittest - { 'target_name': 'libjingle_media_unittest', 'type': 'executable', 'dependencies': [ - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', 'libjingle.gyp:libjingle_media', 'libjingle_unittest_main', ], @@ -157,9 +140,8 @@ 'media/sctp/sctpdataengine_unittest.cc', 'media/webrtc/webrtcpassthroughrender_unittest.cc', 'media/webrtc/webrtcvideocapturer_unittest.cc', - # Omitted because depends on non-open-source testdata files. - # 'media/base/videoframe_unittest.h', - # 'media/webrtc/webrtcvideoframe_unittest.cc', + 'media/base/videoframe_unittest.h', + 'media/webrtc/webrtcvideoframe_unittest.cc', # Disabled because some tests fail. # TODO(ronghuawu): Reenable these tests. @@ -171,6 +153,11 @@ ], 'conditions': [ ['OS=="win"', { + 'dependencies': [ + '<(DEPTH)/net/third_party/nss/ssl.gyp:libssl', + '<(DEPTH)/third_party/nss/nss.gyp:nspr', + '<(DEPTH)/third_party/nss/nss.gyp:nss', + ], 'msvs_settings': { 'VCLinkerTool': { 'AdditionalDependencies': [ @@ -194,7 +181,7 @@ 'type': 'executable', 'dependencies': [ '<(DEPTH)/third_party/libsrtp/libsrtp.gyp:libsrtp', - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', 'libjingle.gyp:libjingle', 'libjingle.gyp:libjingle_p2p', 'libjingle_unittest_main', @@ -253,7 +240,7 @@ 'type': 'executable', 'dependencies': [ '<(DEPTH)/testing/gmock.gyp:gmock', - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', 'libjingle.gyp:libjingle', 'libjingle.gyp:libjingle_p2p', 'libjingle.gyp:libjingle_peerconnection', @@ -386,7 +373,7 @@ 'type': 'executable', 'includes': [ 'build/ios_tests.gypi', ], 'dependencies': [ - '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', 'libjingle.gyp:libjingle_peerconnection_objc', ], 'sources': [ @@ -486,20 +473,6 @@ ], }, { - 'target_name': 'libjingle_sound_unittest_run', - 'type': 'none', - 'dependencies': [ - 'libjingle_sound_unittest', - ], - 'includes': [ - 'build/isolate.gypi', - 'libjingle_sound_unittest.isolate', - ], - 'sources': [ - 'libjingle_sound_unittest.isolate', - ], - }, - { 'target_name': 'libjingle_unittest_run', 'type': 'none', 'dependencies': [ diff --git a/media/base/constants.cc b/media/base/constants.cc index cd10ef7..19a960f 100644 --- a/media/base/constants.cc +++ b/media/base/constants.cc @@ -59,6 +59,7 @@ const char kCodecParamSPropStereo[] = "sprop-stereo"; const char kCodecParamStereo[] = "stereo"; const char kCodecParamUseInbandFec[] = "useinbandfec"; const char kCodecParamMaxAverageBitrate[] = "maxaveragebitrate"; +const char kCodecParamMaxPlaybackRate[] = "maxplaybackrate"; const char kCodecParamSctpProtocol[] = "protocol"; const char kCodecParamSctpStreams[] = "streams"; @@ -72,6 +73,7 @@ const int kOpusDefaultMinPTime = 3; const int kOpusDefaultSPropStereo = 0; const int kOpusDefaultStereo = 0; const int kOpusDefaultUseInbandFec = 0; +const int kOpusDefaultMaxPlaybackRate = 48000; const int kPreferredMaxPTime = 60; const int kPreferredMinPTime = 10; diff --git a/media/base/constants.h b/media/base/constants.h index 5ac1be2..5168acb 100644 --- a/media/base/constants.h +++ b/media/base/constants.h @@ -62,6 +62,7 @@ extern const char kCodecParamSPropStereo[]; extern const char kCodecParamStereo[]; extern const char kCodecParamUseInbandFec[]; extern const char kCodecParamMaxAverageBitrate[]; +extern const char kCodecParamMaxPlaybackRate[]; extern const char kCodecParamSctpProtocol[]; extern const char kCodecParamSctpStreams[]; @@ -79,6 +80,8 @@ extern const int kOpusDefaultMinPTime; extern const int kOpusDefaultSPropStereo; extern const int kOpusDefaultStereo; extern const int kOpusDefaultUseInbandFec; +extern const int kOpusDefaultMaxPlaybackRate; + // Prefered values in this code base. Note that they may differ from the default // values in http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03 // Only frames larger or equal to 10 ms are currently supported in this code diff --git a/media/base/executablehelpers.h b/media/base/executablehelpers.h new file mode 100644 index 0000000..2dde010 --- /dev/null +++ b/media/base/executablehelpers.h @@ -0,0 +1,100 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ +#define TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ + +#ifdef OSX +#include <mach-o/dyld.h> +#endif + +#include <string> + +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" + +namespace rtc { + +// Returns the path to the running executable or an empty path. +// TODO(thorcarpenter): Consolidate with FluteClient::get_executable_dir. +inline Pathname GetExecutablePath() { + const int32 kMaxExePathSize = 255; +#ifdef WIN32 + TCHAR exe_path_buffer[kMaxExePathSize]; + DWORD copied_length = GetModuleFileName(NULL, // NULL = Current process + exe_path_buffer, kMaxExePathSize); + if (0 == copied_length) { + LOG(LS_ERROR) << "Copied length is zero"; + return rtc::Pathname(); + } + if (kMaxExePathSize == copied_length) { + LOG(LS_ERROR) << "Buffer too small"; + return rtc::Pathname(); + } +#ifdef UNICODE + std::wstring wdir(exe_path_buffer); + std::string dir_tmp(wdir.begin(), wdir.end()); + rtc::Pathname path(dir_tmp); +#else // UNICODE + rtc::Pathname path(exe_path_buffer); +#endif // UNICODE +#elif defined(OSX) || defined(LINUX) + char exe_path_buffer[kMaxExePathSize]; +#ifdef OSX + uint32_t copied_length = kMaxExePathSize - 1; + if (_NSGetExecutablePath(exe_path_buffer, &copied_length) == -1) { + LOG(LS_ERROR) << "Buffer too small"; + return rtc::Pathname(); + } +#elif defined LINUX + int32 copied_length = kMaxExePathSize - 1; + const char* kProcExeFmt = "/proc/%d/exe"; + char proc_exe_link[40]; + snprintf(proc_exe_link, sizeof(proc_exe_link), kProcExeFmt, getpid()); + copied_length = readlink(proc_exe_link, exe_path_buffer, copied_length); + if (copied_length == -1) { + LOG_ERR(LS_ERROR) << "Error reading link " << proc_exe_link; + return rtc::Pathname(); + } + if (copied_length == kMaxExePathSize - 1) { + LOG(LS_ERROR) << "Probably truncated result when reading link " + << proc_exe_link; + return rtc::Pathname(); + } + exe_path_buffer[copied_length] = '\0'; +#endif // LINUX + rtc::Pathname path(exe_path_buffer); +#else // Android || IOS + rtc::Pathname path; +#endif // OSX || LINUX + return path; +} + +} // namespace rtc + +#endif // TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ + diff --git a/media/base/fakemediaengine.h b/media/base/fakemediaengine.h index 7bc3958..a6eabef 100644 --- a/media/base/fakemediaengine.h +++ b/media/base/fakemediaengine.h @@ -868,7 +868,7 @@ class FakeVoiceEngine : public FakeBaseEngine { class FakeVideoEngine : public FakeBaseEngine { public: - FakeVideoEngine() : renderer_(NULL), capture_(false), processor_(NULL) { + FakeVideoEngine() : capture_(false), processor_(NULL) { // Add a fake video codec. Note that the name must not be "" as there are // sanity checks against that. codecs_.push_back(VideoCodec(0, "fake_video_codec", 0, 0, 0, 0)); @@ -926,10 +926,6 @@ class FakeVideoEngine : public FakeBaseEngine { options_changed_ = true; return true; } - bool SetLocalRenderer(VideoRenderer* r) { - renderer_ = r; - return true; - } bool SetCapture(bool capture) { capture_ = capture; return true; @@ -946,7 +942,6 @@ class FakeVideoEngine : public FakeBaseEngine { std::vector<VideoCodec> codecs_; VideoEncoderConfig default_encoder_config_; std::string in_device_; - VideoRenderer* renderer_; bool capture_; VideoProcessor* processor_; VideoOptions options_; @@ -994,7 +989,6 @@ class FakeMediaEngine : } const std::string& audio_in_device() const { return voice_.in_device_; } const std::string& audio_out_device() const { return voice_.out_device_; } - VideoRenderer* local_renderer() { return video_.renderer_; } int voice_loglevel() const { return voice_.loglevel_; } const std::string& voice_logfilter() const { return voice_.logfilter_; } int video_loglevel() const { return video_.loglevel_; } diff --git a/media/base/filemediaengine.h b/media/base/filemediaengine.h index e546328..d3e99a8 100644 --- a/media/base/filemediaengine.h +++ b/media/base/filemediaengine.h @@ -88,7 +88,6 @@ class FileMediaEngine : public MediaEngineInterface { virtual SoundclipMedia* CreateSoundclip() { return NULL; } virtual AudioOptions GetAudioOptions() const { return AudioOptions(); } virtual bool SetAudioOptions(const AudioOptions& options) { return true; } - virtual bool SetVideoOptions(const VideoOptions& options) { return true; } virtual bool SetAudioDelayOffset(int offset) { return true; } virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return true; @@ -113,7 +112,6 @@ class FileMediaEngine : public MediaEngineInterface { virtual bool SetOutputVolume(int level) { return true; } virtual int GetInputLevel() { return 0; } virtual bool SetLocalMonitor(bool enable) { return true; } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; } // TODO(whyuan): control channel send? virtual bool SetVideoCapture(bool capture) { return true; } virtual const std::vector<AudioCodec>& audio_codecs() { diff --git a/media/base/filemediaengine_unittest.cc b/media/base/filemediaengine_unittest.cc index 1f7405d..c542baf 100644 --- a/media/base/filemediaengine_unittest.cc +++ b/media/base/filemediaengine_unittest.cc @@ -223,8 +223,6 @@ TEST_F(FileMediaEngineTest, TestDefaultImplementation) { EXPECT_TRUE(NULL == engine_->CreateSoundclip()); cricket::AudioOptions audio_options; EXPECT_TRUE(engine_->SetAudioOptions(audio_options)); - cricket::VideoOptions video_options; - EXPECT_TRUE(engine_->SetVideoOptions(video_options)); VideoEncoderConfig video_encoder_config; EXPECT_TRUE(engine_->SetDefaultVideoEncoderConfig(video_encoder_config)); EXPECT_TRUE(engine_->SetSoundDevices(NULL, NULL)); @@ -232,7 +230,6 @@ TEST_F(FileMediaEngineTest, TestDefaultImplementation) { EXPECT_TRUE(engine_->SetOutputVolume(0)); EXPECT_EQ(0, engine_->GetInputLevel()); EXPECT_TRUE(engine_->SetLocalMonitor(true)); - EXPECT_TRUE(engine_->SetLocalRenderer(NULL)); EXPECT_TRUE(engine_->SetVideoCapture(true)); EXPECT_EQ(0U, engine_->audio_codecs().size()); EXPECT_EQ(0U, engine_->video_codecs().size()); diff --git a/media/base/hybridvideoengine.cc b/media/base/hybridvideoengine.cc deleted file mode 100644 index 289c4fe..0000000 --- a/media/base/hybridvideoengine.cc +++ /dev/null @@ -1,356 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/media/base/hybridvideoengine.h" - -#include "webrtc/base/logging.h" - -namespace cricket { - -HybridVideoMediaChannel::HybridVideoMediaChannel( - HybridVideoEngineInterface* engine, - VideoMediaChannel* channel1, - VideoMediaChannel* channel2) - : engine_(engine), - channel1_(channel1), - channel2_(channel2), - active_channel_(NULL), - sending_(false) { -} - -HybridVideoMediaChannel::~HybridVideoMediaChannel() { -} - -void HybridVideoMediaChannel::SetInterface(NetworkInterface* iface) { - if (channel1_) { - channel1_->SetInterface(iface); - } - if (channel2_) { - channel2_->SetInterface(iface); - } -} - -bool HybridVideoMediaChannel::SetOptions(const VideoOptions &options) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetOptions(options); - } - if (channel2_ && ret) { - ret = channel2_->SetOptions(options); - } - return ret; -} - -bool HybridVideoMediaChannel::GetOptions(VideoOptions *options) const { - if (active_channel_) { - return active_channel_->GetOptions(options); - } - if (channel1_) { - return channel1_->GetOptions(options); - } - if (channel2_) { - return channel2_->GetOptions(options); - } - return false; -} - -bool HybridVideoMediaChannel::SetRecvCodecs( - const std::vector<VideoCodec>& codecs) { - // Only give each channel the codecs it knows about. - bool ret = true; - std::vector<VideoCodec> codecs1, codecs2; - SplitCodecs(codecs, &codecs1, &codecs2); - if (channel1_) { - ret = channel1_->SetRecvCodecs(codecs1); - } - if (channel2_ && ret) { - ret = channel2_->SetRecvCodecs(codecs2); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRecvRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRecvRtpHeaderExtensions(extensions); - } - if (channel2_ && ret) { - ret = channel2_->SetRecvRtpHeaderExtensions(extensions); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRenderer(uint32 ssrc, - VideoRenderer* renderer) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRenderer(ssrc, renderer); - } - if (channel2_ && ret) { - ret = channel2_->SetRenderer(ssrc, renderer); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRender(bool render) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRender(render); - } - if (channel2_ && ret) { - ret = channel2_->SetRender(render); - } - return ret; -} - -bool HybridVideoMediaChannel::MuteStream(uint32 ssrc, bool muted) { - bool ret = true; - if (channel1_) { - ret = channel1_->MuteStream(ssrc, muted); - } - if (channel2_ && ret) { - ret = channel2_->MuteStream(ssrc, muted); - } - return ret; -} - -bool HybridVideoMediaChannel::SetSendCodecs( - const std::vector<VideoCodec>& codecs) { - // Use the input to this function to decide what impl we're going to use. - if (!active_channel_ && !SelectActiveChannel(codecs)) { - LOG(LS_WARNING) << "Failed to select active channel"; - return false; - } - // Only give the active channel the codecs it knows about. - std::vector<VideoCodec> codecs1, codecs2; - SplitCodecs(codecs, &codecs1, &codecs2); - const std::vector<VideoCodec>& codecs_to_set = - (active_channel_ == channel1_.get()) ? codecs1 : codecs2; - bool return_value = active_channel_->SetSendCodecs(codecs_to_set); - if (!return_value) { - return false; - } - VideoCodec send_codec; - return_value = active_channel_->GetSendCodec(&send_codec); - if (!return_value) { - return false; - } - engine_->OnNewSendResolution(send_codec.width, send_codec.height); - active_channel_->UpdateAspectRatio(send_codec.width, send_codec.height); - return true; -} - -bool HybridVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) { - if (!active_channel_) { - return false; - } - return active_channel_->GetSendCodec(send_codec); -} - -bool HybridVideoMediaChannel::SetSendStreamFormat(uint32 ssrc, - const VideoFormat& format) { - return active_channel_ && active_channel_->SetSendStreamFormat(ssrc, format); -} - -bool HybridVideoMediaChannel::SetSendRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions) { - return active_channel_ && - active_channel_->SetSendRtpHeaderExtensions(extensions); -} - -bool HybridVideoMediaChannel::SetStartSendBandwidth(int bps) { - return active_channel_ && active_channel_->SetStartSendBandwidth(bps); -} - -bool HybridVideoMediaChannel::SetMaxSendBandwidth(int bps) { - return active_channel_ && active_channel_->SetMaxSendBandwidth(bps); -} - -bool HybridVideoMediaChannel::SetSend(bool send) { - if (send == sending()) { - return true; // no action required if already set. - } - - bool ret = active_channel_ && - active_channel_->SetSend(send); - - // Returns error and don't connect the signal if starting up. - // Disconnects the signal anyway if shutting down. - if (ret || !send) { - // TODO(juberti): Remove this hack that connects the WebRTC channel - // to the capturer. - if (active_channel_ == channel1_.get()) { - engine_->OnSendChange1(channel1_.get(), send); - } else { - engine_->OnSendChange2(channel2_.get(), send); - } - // If succeeded, remember the state as is. - // If failed to open, sending_ should be false. - // If failed to stop, sending_ should also be false, as we disconnect the - // capture anyway. - // The failure on SetSend(false) is a known issue in webrtc. - sending_ = send; - } - return ret; -} - -bool HybridVideoMediaChannel::SetCapturer(uint32 ssrc, - VideoCapturer* capturer) { - bool ret = true; - if (channel1_.get()) { - ret = channel1_->SetCapturer(ssrc, capturer); - } - if (channel2_.get() && ret) { - ret = channel2_->SetCapturer(ssrc, capturer); - } - return ret; -} - -bool HybridVideoMediaChannel::AddSendStream(const StreamParams& sp) { - bool ret = true; - if (channel1_) { - ret = channel1_->AddSendStream(sp); - } - if (channel2_ && ret) { - ret = channel2_->AddSendStream(sp); - } - return ret; -} - -bool HybridVideoMediaChannel::RemoveSendStream(uint32 ssrc) { - bool ret = true; - if (channel1_) { - ret = channel1_->RemoveSendStream(ssrc); - } - if (channel2_ && ret) { - ret = channel2_->RemoveSendStream(ssrc); - } - return ret; -} - -bool HybridVideoMediaChannel::AddRecvStream(const StreamParams& sp) { - return active_channel_ && - active_channel_->AddRecvStream(sp); -} - -bool HybridVideoMediaChannel::RemoveRecvStream(uint32 ssrc) { - return active_channel_ && - active_channel_->RemoveRecvStream(ssrc); -} - -bool HybridVideoMediaChannel::SendIntraFrame() { - return active_channel_ && - active_channel_->SendIntraFrame(); -} - -bool HybridVideoMediaChannel::RequestIntraFrame() { - return active_channel_ && - active_channel_->RequestIntraFrame(); -} - -bool HybridVideoMediaChannel::GetStats( - const StatsOptions& options, VideoMediaInfo* info) { - // TODO(juberti): Ensure that returning no stats until SetSendCodecs is OK. - return active_channel_ && - active_channel_->GetStats(options, info); -} - -void HybridVideoMediaChannel::OnPacketReceived( - rtc::Buffer* packet, const rtc::PacketTime& packet_time) { - // Eat packets until we have an active channel; - if (active_channel_) { - active_channel_->OnPacketReceived(packet, packet_time); - } else { - LOG(LS_INFO) << "HybridVideoChannel: Eating early RTP packet"; - } -} - -void HybridVideoMediaChannel::OnRtcpReceived( - rtc::Buffer* packet, const rtc::PacketTime& packet_time) { - // Eat packets until we have an active channel; - if (active_channel_) { - active_channel_->OnRtcpReceived(packet, packet_time); - } else { - LOG(LS_INFO) << "HybridVideoChannel: Eating early RTCP packet"; - } -} - -void HybridVideoMediaChannel::OnReadyToSend(bool ready) { - if (channel1_) { - channel1_->OnReadyToSend(ready); - } - if (channel2_) { - channel2_->OnReadyToSend(ready); - } -} - -void HybridVideoMediaChannel::UpdateAspectRatio(int ratio_w, int ratio_h) { - if (active_channel_) active_channel_->UpdateAspectRatio(ratio_w, ratio_h); -} - -bool HybridVideoMediaChannel::SelectActiveChannel( - const std::vector<VideoCodec>& codecs) { - if (!active_channel_ && !codecs.empty()) { - if (engine_->HasCodec1(codecs[0])) { - channel2_.reset(); - active_channel_ = channel1_.get(); - } else if (engine_->HasCodec2(codecs[0])) { - channel1_.reset(); - active_channel_ = channel2_.get(); - } - } - if (NULL == active_channel_) { - return false; - } - // Connect signals from the active channel. - active_channel_->SignalMediaError.connect( - this, - &HybridVideoMediaChannel::OnMediaError); - return true; -} - -void HybridVideoMediaChannel::SplitCodecs( - const std::vector<VideoCodec>& codecs, - std::vector<VideoCodec>* codecs1, std::vector<VideoCodec>* codecs2) { - codecs1->clear(); - codecs2->clear(); - for (size_t i = 0; i < codecs.size(); ++i) { - if (engine_->HasCodec1(codecs[i])) { - codecs1->push_back(codecs[i]); - } - if (engine_->HasCodec2(codecs[i])) { - codecs2->push_back(codecs[i]); - } - } -} - -void HybridVideoMediaChannel::OnMediaError(uint32 ssrc, Error error) { - SignalMediaError(ssrc, error); -} - -} // namespace cricket diff --git a/media/base/hybridvideoengine.h b/media/base/hybridvideoengine.h deleted file mode 100644 index 004d3cf..0000000 --- a/media/base/hybridvideoengine.h +++ /dev/null @@ -1,286 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ -#define TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ - -#include <string> -#include <vector> - -#include "talk/media/base/codec.h" -#include "talk/media/base/mediachannel.h" -#include "talk/media/base/videocapturer.h" -#include "talk/media/base/videocommon.h" -#include "webrtc/base/logging.h" -#include "webrtc/base/sigslotrepeater.h" - -namespace cricket { - -struct Device; -struct VideoFormat; -class HybridVideoEngineInterface; -class VideoCapturer; -class VideoFrame; -class VideoRenderer; - -// HybridVideoMediaChannels work with a HybridVideoEngine to combine -// two unrelated VideoMediaChannel implementations into a single class. -class HybridVideoMediaChannel : public VideoMediaChannel { - public: - HybridVideoMediaChannel(HybridVideoEngineInterface* engine, - VideoMediaChannel* channel1, - VideoMediaChannel* channel2); - virtual ~HybridVideoMediaChannel(); - - // VideoMediaChannel methods - virtual void SetInterface(NetworkInterface* iface); - virtual bool SetOptions(const VideoOptions& options); - virtual bool GetOptions(VideoOptions* options) const; - virtual bool AddSendStream(const StreamParams& sp); - virtual bool RemoveSendStream(uint32 ssrc); - virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer); - virtual bool SetRender(bool render); - virtual bool MuteStream(uint32 ssrc, bool muted); - - virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs); - virtual bool SetRecvRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions); - - virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs); - virtual bool GetSendCodec(VideoCodec* codec); - virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format); - virtual bool SetSendRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions); - virtual bool SetStartSendBandwidth(int bps); - virtual bool SetMaxSendBandwidth(int bps); - virtual bool SetSend(bool send); - - virtual bool AddRecvStream(const StreamParams& sp); - virtual bool RemoveRecvStream(uint32 ssrc); - virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer); - - virtual bool SendIntraFrame(); - virtual bool RequestIntraFrame(); - - virtual bool GetStats(const StatsOptions& options, VideoMediaInfo* info); - - virtual void OnPacketReceived(rtc::Buffer* packet, - const rtc::PacketTime& packet_time); - virtual void OnRtcpReceived(rtc::Buffer* packet, - const rtc::PacketTime& packet_time); - virtual void OnReadyToSend(bool ready); - - virtual void UpdateAspectRatio(int ratio_w, int ratio_h); - - void OnLocalFrame(VideoCapturer*, const VideoFrame*); - void OnLocalFrameFormat(VideoCapturer*, const VideoFormat*); - - bool sending() const { return sending_; } - - private: - bool SelectActiveChannel(const std::vector<VideoCodec>& codecs); - void SplitCodecs(const std::vector<VideoCodec>& codecs, - std::vector<VideoCodec>* codecs1, - std::vector<VideoCodec>* codecs2); - - void OnMediaError(uint32 ssrc, Error error); - - HybridVideoEngineInterface* engine_; - rtc::scoped_ptr<VideoMediaChannel> channel1_; - rtc::scoped_ptr<VideoMediaChannel> channel2_; - VideoMediaChannel* active_channel_; - bool sending_; -}; - -// Interface class for HybridVideoChannels to talk to the engine. -class HybridVideoEngineInterface { - public: - virtual ~HybridVideoEngineInterface() {} - virtual bool HasCodec1(const VideoCodec& codec) = 0; - virtual bool HasCodec2(const VideoCodec& codec) = 0; - virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) = 0; - virtual void OnSendChange2(VideoMediaChannel* channel1, bool send) = 0; - virtual void OnNewSendResolution(int width, int height) = 0; -}; - -// The HybridVideoEngine class combines two unrelated VideoEngine impls -// into a single class. It creates HybridVideoMediaChannels that also contain -// a VideoMediaChannel implementation from each engine. Policy is then used -// during call setup to determine which VideoMediaChannel should be used. -// Currently, this policy is based on what codec the remote side wants to use. -template<class VIDEO1, class VIDEO2> -class HybridVideoEngine : public HybridVideoEngineInterface { - public: - HybridVideoEngine() { - // Unify the codec lists. - codecs_ = video1_.codecs(); - codecs_.insert(codecs_.end(), video2_.codecs().begin(), - video2_.codecs().end()); - - rtp_header_extensions_ = video1_.rtp_header_extensions(); - rtp_header_extensions_.insert(rtp_header_extensions_.end(), - video2_.rtp_header_extensions().begin(), - video2_.rtp_header_extensions().end()); - - SignalCaptureStateChange.repeat(video2_.SignalCaptureStateChange); - } - - bool Init(rtc::Thread* worker_thread) { - if (!video1_.Init(worker_thread)) { - LOG(LS_ERROR) << "Failed to init VideoEngine1"; - return false; - } - if (!video2_.Init(worker_thread)) { - LOG(LS_ERROR) << "Failed to init VideoEngine2"; - video1_.Terminate(); - return false; - } - return true; - } - void Terminate() { - video1_.Terminate(); - video2_.Terminate(); - } - - int GetCapabilities() { - return (video1_.GetCapabilities() | video2_.GetCapabilities()); - } - HybridVideoMediaChannel* CreateChannel(VoiceMediaChannel* channel) { - rtc::scoped_ptr<VideoMediaChannel> channel1( - video1_.CreateChannel(channel)); - if (!channel1) { - LOG(LS_ERROR) << "Failed to create VideoMediaChannel1"; - return NULL; - } - rtc::scoped_ptr<VideoMediaChannel> channel2( - video2_.CreateChannel(channel)); - if (!channel2) { - LOG(LS_ERROR) << "Failed to create VideoMediaChannel2"; - return NULL; - } - return new HybridVideoMediaChannel(this, - channel1.release(), channel2.release()); - } - - bool SetOptions(const VideoOptions& options) { - return video1_.SetOptions(options) && video2_.SetOptions(options); - } - bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) { - VideoEncoderConfig conf = config; - if (video1_.codecs().size() > 0) { - conf.max_codec.name = video1_.codecs()[0].name; - if (!video1_.SetDefaultEncoderConfig(conf)) { - LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video1"; - return false; - } - } - if (video2_.codecs().size() > 0) { - conf.max_codec.name = video2_.codecs()[0].name; - if (!video2_.SetDefaultEncoderConfig(conf)) { - LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video2"; - return false; - } - } - return true; - } - VideoEncoderConfig GetDefaultEncoderConfig() const { - // This looks pretty strange, but, in practice, it'll do sane things if - // GetDefaultEncoderConfig is only called after SetDefaultEncoderConfig, - // since both engines should be essentially equivalent at that point. If it - // hasn't been called, though, we'll use the first meaningful encoder - // config, or the config from the second video engine if neither are - // meaningful. - VideoEncoderConfig config = video1_.GetDefaultEncoderConfig(); - if (config.max_codec.width != 0) { - return config; - } else { - return video2_.GetDefaultEncoderConfig(); - } - } - const std::vector<VideoCodec>& codecs() const { - return codecs_; - } - const std::vector<RtpHeaderExtension>& rtp_header_extensions() const { - return rtp_header_extensions_; - } - void SetLogging(int min_sev, const char* filter) { - video1_.SetLogging(min_sev, filter); - video2_.SetLogging(min_sev, filter); - } - - VideoFormat GetStartCaptureFormat() const { - return video2_.GetStartCaptureFormat(); - } - - // TODO(juberti): Remove these functions after we do the capturer refactoring. - // For now they are set to always use the second engine for capturing, which - // is convenient given our intended use case. - bool SetCaptureDevice(const Device* device) { - return video2_.SetCaptureDevice(device); - } - VideoCapturer* GetVideoCapturer() const { - return video2_.GetVideoCapturer(); - } - bool SetLocalRenderer(VideoRenderer* renderer) { - return video2_.SetLocalRenderer(renderer); - } - sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; - - virtual bool HasCodec1(const VideoCodec& codec) { - return HasCodec(video1_, codec); - } - virtual bool HasCodec2(const VideoCodec& codec) { - return HasCodec(video2_, codec); - } - template<typename VIDEO> - bool HasCodec(const VIDEO& engine, const VideoCodec& codec) const { - for (std::vector<VideoCodec>::const_iterator i = engine.codecs().begin(); - i != engine.codecs().end(); - ++i) { - if (i->Matches(codec)) { - return true; - } - } - return false; - } - virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) { - } - virtual void OnSendChange2(VideoMediaChannel* channel2, bool send) { - } - virtual void OnNewSendResolution(int width, int height) { - } - - protected: - VIDEO1 video1_; - VIDEO2 video2_; - std::vector<VideoCodec> codecs_; - std::vector<RtpHeaderExtension> rtp_header_extensions_; -}; - -} // namespace cricket - -#endif // TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ diff --git a/media/base/hybridvideoengine_unittest.cc b/media/base/hybridvideoengine_unittest.cc deleted file mode 100644 index 7b409ea..0000000 --- a/media/base/hybridvideoengine_unittest.cc +++ /dev/null @@ -1,486 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/media/base/fakemediaengine.h" -#include "talk/media/base/fakenetworkinterface.h" -#include "talk/media/base/fakevideocapturer.h" -#include "talk/media/base/hybridvideoengine.h" -#include "talk/media/base/mediachannel.h" -#include "talk/media/base/testutils.h" -#include "webrtc/base/gunit.h" - -static const cricket::VideoCodec kGenericCodec(97, "Generic", 640, 360, 30, 0); -static const cricket::VideoCodec kVp8Codec(100, "VP8", 640, 360, 30, 0); -static const cricket::VideoCodec kCodecsVp8Only[] = { kVp8Codec }; -static const cricket::VideoCodec kCodecsGenericOnly[] = { kGenericCodec }; -static const cricket::VideoCodec kCodecsVp8First[] = { kVp8Codec, - kGenericCodec }; -static const cricket::VideoCodec kCodecsGenericFirst[] = { kGenericCodec, - kVp8Codec }; - -using cricket::StreamParams; - -class FakeVp8VideoEngine : public cricket::FakeVideoEngine { - public: - FakeVp8VideoEngine() { - SetCodecs(MAKE_VECTOR(kCodecsVp8Only)); - } -}; -class FakeGenericVideoEngine : public cricket::FakeVideoEngine { - public: - FakeGenericVideoEngine() { - SetCodecs(MAKE_VECTOR(kCodecsGenericOnly)); - } - - // For testing purposes, mimic the behavior of a media engine that throws out - // resolutions that don't match the codec list. A width or height of 0 - // trivially will never match the codec list, so this is sufficient for - // testing the case we want (0x0). - virtual bool FindCodec(const cricket::VideoCodec& codec) { - if (codec.width == 0 || codec.height == 0) { - return false; - } else { - return cricket::FakeVideoEngine::FindCodec(codec); - } - } -}; -class HybridVideoEngineForTest : public cricket::HybridVideoEngine< - FakeVp8VideoEngine, FakeGenericVideoEngine> { - public: - HybridVideoEngineForTest() - : - num_ch1_send_on_(0), - num_ch1_send_off_(0), - send_width_(0), - send_height_(0) { } - cricket::FakeVideoEngine* sub_engine1() { return &video1_; } - cricket::FakeVideoEngine* sub_engine2() { return &video2_; } - - // From base class HybridVideoEngine. - void OnSendChange1(cricket::VideoMediaChannel* channel1, bool send) { - if (send) { - ++num_ch1_send_on_; - } else { - ++num_ch1_send_off_; - } - } - // From base class HybridVideoEngine - void OnNewSendResolution(int width, int height) { - send_width_ = width; - send_height_ = height; - } - - int num_ch1_send_on() const { return num_ch1_send_on_; } - int num_ch1_send_off() const { return num_ch1_send_off_; } - - int send_width() const { return send_width_; } - int send_height() const { return send_height_; } - - private: - int num_ch1_send_on_; - int num_ch1_send_off_; - - int send_width_; - int send_height_; -}; - -class HybridVideoEngineTest : public testing::Test { - public: - HybridVideoEngineTest() : sub_channel1_(NULL), sub_channel2_(NULL) { - } - ~HybridVideoEngineTest() { - engine_.Terminate(); - } - bool SetupEngine() { - bool result = engine_.Init(rtc::Thread::Current()); - if (result) { - channel_.reset(engine_.CreateChannel(NULL)); - result = (channel_.get() != NULL); - sub_channel1_ = engine_.sub_engine1()->GetChannel(0); - sub_channel2_ = engine_.sub_engine2()->GetChannel(0); - } - return result; - } - bool SetupRenderAndAddStream(const StreamParams& sp) { - if (!SetupEngine()) - return false; - channel_->SetInterface(transport_.get()); - return channel_->SetRecvCodecs(engine_.codecs()) && - channel_->AddSendStream(sp) && - channel_->SetRender(true); - } - void DeliverPacket(const void* data, int len) { - rtc::Buffer packet(data, len); - channel_->OnPacketReceived(&packet, rtc::CreatePacketTime(0)); - } - void DeliverRtcp(const void* data, int len) { - rtc::Buffer packet(data, len); - channel_->OnRtcpReceived(&packet, rtc::CreatePacketTime(0)); - } - - protected: - void TestSetSendCodecs(cricket::FakeVideoEngine* sub_engine, - const std::vector<cricket::VideoCodec>& codecs) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); - cricket::FakeVideoMediaChannel* sub_channel = sub_engine->GetChannel(0); - ASSERT_EQ(1U, sub_channel->send_codecs().size()); - EXPECT_EQ(codecs[0], sub_channel->send_codecs()[0]); - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(sub_channel->sending()); - } - void TestSetSendBandwidth(cricket::FakeVideoEngine* sub_engine, - const std::vector<cricket::VideoCodec>& codecs, - int start_bitrate, - int max_bitrate) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); - EXPECT_TRUE(channel_->SetStartSendBandwidth(start_bitrate)); - EXPECT_TRUE(channel_->SetMaxSendBandwidth(max_bitrate)); - cricket::FakeVideoMediaChannel* sub_channel = sub_engine->GetChannel(0); - EXPECT_EQ(start_bitrate, sub_channel->start_bps()); - EXPECT_EQ(max_bitrate, sub_channel->max_bps()); - } - HybridVideoEngineForTest engine_; - rtc::scoped_ptr<cricket::HybridVideoMediaChannel> channel_; - rtc::scoped_ptr<cricket::FakeNetworkInterface> transport_; - cricket::FakeVideoMediaChannel* sub_channel1_; - cricket::FakeVideoMediaChannel* sub_channel2_; -}; - -TEST_F(HybridVideoEngineTest, StartupShutdown) { - EXPECT_TRUE(engine_.Init(rtc::Thread::Current())); - engine_.Terminate(); -} - -// Tests that SetDefaultVideoEncoderConfig passes down to both engines. -TEST_F(HybridVideoEngineTest, SetDefaultVideoEncoderConfig) { - cricket::VideoEncoderConfig config( - cricket::VideoCodec(105, "", 640, 400, 30, 0), 1, 2); - EXPECT_TRUE(engine_.SetDefaultEncoderConfig(config)); - - cricket::VideoEncoderConfig config_1 = config; - config_1.max_codec.name = kCodecsVp8Only[0].name; - EXPECT_EQ(config_1, engine_.sub_engine1()->default_encoder_config()); - - cricket::VideoEncoderConfig config_2 = config; - config_2.max_codec.name = kCodecsGenericOnly[0].name; - EXPECT_EQ(config_2, engine_.sub_engine2()->default_encoder_config()); -} - -// Tests that GetDefaultVideoEncoderConfig picks a meaningful encoder config -// based on the underlying engine config and then after a call to -// SetDefaultEncoderConfig on the hybrid engine. -TEST_F(HybridVideoEngineTest, SetDefaultVideoEncoderConfigDefaultValue) { - cricket::VideoEncoderConfig blank_config; - cricket::VideoEncoderConfig meaningful_config1( - cricket::VideoCodec(111, "abcd", 320, 240, 30, 0), 1, 2); - cricket::VideoEncoderConfig meaningful_config2( - cricket::VideoCodec(111, "abcd", 1280, 720, 30, 0), 1, 2); - cricket::VideoEncoderConfig meaningful_config3( - cricket::VideoCodec(111, "abcd", 640, 360, 30, 0), 1, 2); - engine_.sub_engine1()->SetDefaultEncoderConfig(blank_config); - engine_.sub_engine2()->SetDefaultEncoderConfig(blank_config); - EXPECT_EQ(blank_config, engine_.GetDefaultEncoderConfig()); - - engine_.sub_engine2()->SetDefaultEncoderConfig(meaningful_config2); - EXPECT_EQ(meaningful_config2, engine_.GetDefaultEncoderConfig()); - - engine_.sub_engine1()->SetDefaultEncoderConfig(meaningful_config1); - EXPECT_EQ(meaningful_config1, engine_.GetDefaultEncoderConfig()); - - EXPECT_TRUE(engine_.SetDefaultEncoderConfig(meaningful_config3)); - // The overall config should now match, though the codec name will have been - // rewritten for the first media engine. - meaningful_config3.max_codec.name = kCodecsVp8Only[0].name; - EXPECT_EQ(meaningful_config3, engine_.GetDefaultEncoderConfig()); -} - -// Tests that our engine has the right codecs in the right order. -TEST_F(HybridVideoEngineTest, CheckCodecs) { - const std::vector<cricket::VideoCodec>& c = engine_.codecs(); - ASSERT_EQ(2U, c.size()); - EXPECT_EQ(kVp8Codec, c[0]); - EXPECT_EQ(kGenericCodec, c[1]); -} - -// Tests that our engine has the right caps. -TEST_F(HybridVideoEngineTest, CheckCaps) { - EXPECT_EQ(cricket::VIDEO_SEND | cricket::VIDEO_RECV, - engine_.GetCapabilities()); -} - -// Tests that we can create and destroy a channel. -TEST_F(HybridVideoEngineTest, CreateChannel) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(sub_channel1_ != NULL); - EXPECT_TRUE(sub_channel2_ != NULL); -} - -// Tests that we properly handle failures in CreateChannel. -TEST_F(HybridVideoEngineTest, CreateChannelFail) { - engine_.sub_engine1()->set_fail_create_channel(true); - EXPECT_FALSE(SetupEngine()); - EXPECT_TRUE(channel_.get() == NULL); - EXPECT_TRUE(sub_channel1_ == NULL); - EXPECT_TRUE(sub_channel2_ == NULL); - engine_.sub_engine1()->set_fail_create_channel(false); - engine_.sub_engine2()->set_fail_create_channel(true); - EXPECT_FALSE(SetupEngine()); - EXPECT_TRUE(channel_.get() == NULL); - EXPECT_TRUE(sub_channel1_ == NULL); - EXPECT_TRUE(sub_channel2_ == NULL); -} - -// Test that we set our inbound codecs and settings properly. -TEST_F(HybridVideoEngineTest, SetLocalDescription) { - EXPECT_TRUE(SetupEngine()); - channel_->SetInterface(transport_.get()); - EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs())); - ASSERT_EQ(1U, sub_channel1_->recv_codecs().size()); - ASSERT_EQ(1U, sub_channel2_->recv_codecs().size()); - EXPECT_EQ(kVp8Codec, sub_channel1_->recv_codecs()[0]); - EXPECT_EQ(kGenericCodec, sub_channel2_->recv_codecs()[0]); - StreamParams stream; - stream.id = "TestStream"; - stream.ssrcs.push_back(1234); - stream.cname = "5678"; - EXPECT_TRUE(channel_->AddSendStream(stream)); - EXPECT_EQ(1234U, sub_channel1_->send_ssrc()); - EXPECT_EQ(1234U, sub_channel2_->send_ssrc()); - EXPECT_EQ("5678", sub_channel1_->rtcp_cname()); - EXPECT_EQ("5678", sub_channel2_->rtcp_cname()); - EXPECT_TRUE(channel_->SetRender(true)); - // We've called SetRender, so we should be playing out, but not yet sending. - EXPECT_TRUE(sub_channel1_->playout()); - EXPECT_TRUE(sub_channel2_->playout()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_FALSE(sub_channel2_->sending()); - // We may get SetSend(false) calls during call setup. - // Since this causes no change in state, they should no-op and return true. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_FALSE(sub_channel2_->sending()); -} - -TEST_F(HybridVideoEngineTest, OnNewSendResolution) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - EXPECT_EQ(640, engine_.send_width()); - EXPECT_EQ(360, engine_.send_height()); -} - -// Test that we converge to the active channel for engine 1. -TEST_F(HybridVideoEngineTest, SetSendCodecs1) { - // This will nuke the object that sub_channel2_ points to. - TestSetSendCodecs(engine_.sub_engine1(), MAKE_VECTOR(kCodecsVp8First)); - EXPECT_TRUE(engine_.sub_engine2()->GetChannel(0) == NULL); -} - -// Test that we converge to the active channel for engine 2. -TEST_F(HybridVideoEngineTest, SetSendCodecs2) { - // This will nuke the object that sub_channel1_ points to. - TestSetSendCodecs(engine_.sub_engine2(), MAKE_VECTOR(kCodecsGenericFirst)); - EXPECT_TRUE(engine_.sub_engine1()->GetChannel(0) == NULL); -} - -// Test that we don't accidentally eat 0x0 in SetSendCodecs -TEST_F(HybridVideoEngineTest, SetSendCodecs0x0) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - // Send using generic codec, but with 0x0 resolution. - std::vector<cricket::VideoCodec> codecs(MAKE_VECTOR(kCodecsGenericFirst)); - codecs.resize(1); - codecs[0].width = 0; - codecs[0].height = 0; - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); -} - -// Test setting the send bandwidth for VP8. -TEST_F(HybridVideoEngineTest, SetSendBandwidth1) { - TestSetSendBandwidth(engine_.sub_engine1(), - MAKE_VECTOR(kCodecsVp8First), - 100000, - 384000); -} - -// Test setting the send bandwidth for a generic codec. -TEST_F(HybridVideoEngineTest, SetSendBandwidth2) { - TestSetSendBandwidth(engine_.sub_engine2(), - MAKE_VECTOR(kCodecsGenericFirst), - 100001, - 384002); -} - -// Test that we dump RTP packets that arrive early. -TEST_F(HybridVideoEngineTest, HandleEarlyRtp) { - static const uint8 kPacket[1024] = { 0 }; - static const uint8 kRtcp[1024] = { 1 }; - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - DeliverPacket(kPacket, sizeof(kPacket)); - DeliverRtcp(kRtcp, sizeof(kRtcp)); - EXPECT_TRUE(sub_channel1_->CheckNoRtp()); - EXPECT_TRUE(sub_channel2_->CheckNoRtp()); - EXPECT_TRUE(sub_channel1_->CheckNoRtcp()); - EXPECT_TRUE(sub_channel2_->CheckNoRtcp()); -} - -// Test that we properly pass on normal RTP packets. -TEST_F(HybridVideoEngineTest, HandleRtp) { - static const uint8 kPacket[1024] = { 0 }; - static const uint8 kRtcp[1024] = { 1 }; - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - EXPECT_TRUE(channel_->SetSend(true)); - DeliverPacket(kPacket, sizeof(kPacket)); - DeliverRtcp(kRtcp, sizeof(kRtcp)); - EXPECT_TRUE(sub_channel1_->CheckRtp(kPacket, sizeof(kPacket))); - EXPECT_TRUE(sub_channel1_->CheckRtcp(kRtcp, sizeof(kRtcp))); -} - -// Test that we properly connect media error signal. -TEST_F(HybridVideoEngineTest, MediaErrorSignal) { - cricket::VideoMediaErrorCatcher catcher; - - // Verify no signal from either channel before the active channel is set. - EXPECT_TRUE(SetupEngine()); - channel_->SignalMediaError.connect(&catcher, - &cricket::VideoMediaErrorCatcher::OnError); - sub_channel1_->SignalMediaError(1, cricket::VideoMediaChannel::ERROR_OTHER); - EXPECT_EQ(0U, catcher.ssrc()); - sub_channel2_->SignalMediaError(2, - cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED); - EXPECT_EQ(0U, catcher.ssrc()); - - // Set vp8 as active channel and verify that a signal comes from it. - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - sub_channel1_->SignalMediaError(1, cricket::VideoMediaChannel::ERROR_OTHER); - EXPECT_EQ(cricket::VideoMediaChannel::ERROR_OTHER, catcher.error()); - EXPECT_EQ(1U, catcher.ssrc()); - - // Set generic codec as active channel and verify that a signal comes from it. - EXPECT_TRUE(SetupEngine()); - channel_->SignalMediaError.connect(&catcher, - &cricket::VideoMediaErrorCatcher::OnError); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsGenericFirst))); - sub_channel2_->SignalMediaError(2, - cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED); - EXPECT_EQ(cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED, - catcher.error()); - EXPECT_EQ(2U, catcher.ssrc()); -} - -// Test that SetSend doesn't re-enter. -TEST_F(HybridVideoEngineTest, RepeatSetSend) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - - // Verify initial status. - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(0, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // Verfiy SetSend(true) works correctly. - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(channel_->sending()); - EXPECT_TRUE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // SetSend(true) again and verify nothing changes. - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(channel_->sending()); - EXPECT_TRUE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // Verify SetSend(false) works correctly. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(1, engine_.num_ch1_send_off()); - - // SetSend(false) again and verfiy nothing changes. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(1, engine_.num_ch1_send_off()); -} - -// Test that SetOptions. -TEST_F(HybridVideoEngineTest, SetOptions) { - cricket::VideoOptions vmo; - vmo.video_high_bitrate.Set(true); - vmo.system_low_adaptation_threshhold.Set(0.10f); - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetOptions(vmo)); - - bool high_bitrate; - float low; - EXPECT_TRUE(sub_channel1_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_TRUE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.10f, low); - EXPECT_TRUE(sub_channel2_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_TRUE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.10f, low); - - vmo.video_high_bitrate.Set(false); - vmo.system_low_adaptation_threshhold.Set(0.50f); - - EXPECT_TRUE(channel_->SetOptions(vmo)); - EXPECT_TRUE(sub_channel1_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_FALSE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.50f, low); - EXPECT_TRUE(sub_channel2_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_FALSE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.50f, low); -} - -TEST_F(HybridVideoEngineTest, SetCapturer) { - EXPECT_TRUE(SetupEngine()); - // Set vp8 as active channel and verify that capturer can be set. - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - cricket::FakeVideoCapturer fake_video_capturer; - EXPECT_TRUE(channel_->SetCapturer(0, &fake_video_capturer)); - EXPECT_TRUE(channel_->SetCapturer(0, NULL)); - - // Set generic codec active channel and verify that capturer can be set. - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsGenericFirst))); - EXPECT_TRUE(channel_->SetCapturer(0, &fake_video_capturer)); - EXPECT_TRUE(channel_->SetCapturer(0, NULL)); -} diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h index 62d6b61..5232e5d 100644 --- a/media/base/mediachannel.h +++ b/media/base/mediachannel.h @@ -182,6 +182,7 @@ struct AudioOptions { recording_sample_rate.SetFrom(change.recording_sample_rate); playout_sample_rate.SetFrom(change.playout_sample_rate); dscp.SetFrom(change.dscp); + combined_audio_video_bwe.SetFrom(change.combined_audio_video_bwe); } bool operator==(const AudioOptions& o) const { @@ -207,7 +208,8 @@ struct AudioOptions { rx_agc_limiter == o.rx_agc_limiter && recording_sample_rate == o.recording_sample_rate && playout_sample_rate == o.playout_sample_rate && - dscp == o.dscp; + dscp == o.dscp && + combined_audio_video_bwe == o.combined_audio_video_bwe; } std::string ToString() const { @@ -238,6 +240,7 @@ struct AudioOptions { ost << ToStringIfSet("recording_sample_rate", recording_sample_rate); ost << ToStringIfSet("playout_sample_rate", playout_sample_rate); ost << ToStringIfSet("dscp", dscp); + ost << ToStringIfSet("combined_audio_video_bwe", combined_audio_video_bwe); ost << "}"; return ost.str(); } @@ -275,6 +278,8 @@ struct AudioOptions { Settable<uint32> playout_sample_rate; // Set DSCP value for packet sent from audio channel. Settable<bool> dscp; + // Enable combined audio+bandwidth BWE. + Settable<bool> combined_audio_video_bwe; }; // Options that can be applied to a VideoMediaChannel or a VideoMediaEngine. diff --git a/media/base/mediaengine.h b/media/base/mediaengine.h index 6c4b740..f30e3b1 100644 --- a/media/base/mediaengine.h +++ b/media/base/mediaengine.h @@ -91,8 +91,6 @@ class MediaEngineInterface { virtual AudioOptions GetAudioOptions() const = 0; // Sets global audio options. "options" are from AudioOptions, above. virtual bool SetAudioOptions(const AudioOptions& options) = 0; - // Sets global video options. "options" are from VideoOptions, above. - virtual bool SetVideoOptions(const VideoOptions& options) = 0; // Sets the value used by the echo canceller to offset delay values obtained // from the OS. virtual bool SetAudioDelayOffset(int offset) = 0; @@ -124,7 +122,6 @@ class MediaEngineInterface { // when a VoiceMediaChannel starts sending. virtual bool SetLocalMonitor(bool enable) = 0; // Installs a callback for raw frames from the local camera. - virtual bool SetLocalRenderer(VideoRenderer* renderer) = 0; virtual const std::vector<AudioCodec>& audio_codecs() = 0; virtual const std::vector<RtpHeaderExtension>& @@ -214,9 +211,6 @@ class CompositeMediaEngine : public MediaEngineInterface { virtual bool SetAudioOptions(const AudioOptions& options) { return voice_.SetOptions(options); } - virtual bool SetVideoOptions(const VideoOptions& options) { - return video_.SetOptions(options); - } virtual bool SetAudioDelayOffset(int offset) { return voice_.SetDelayOffset(offset); } @@ -245,10 +239,6 @@ class CompositeMediaEngine : public MediaEngineInterface { virtual bool SetLocalMonitor(bool enable) { return voice_.SetLocalMonitor(enable); } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { - return video_.SetLocalRenderer(renderer); - } - virtual const std::vector<AudioCodec>& audio_codecs() { return voice_.codecs(); } @@ -361,7 +351,6 @@ class NullVideoEngine { bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) { return true; } - bool SetLocalRenderer(VideoRenderer* renderer) { return true; } const std::vector<VideoCodec>& codecs() { return codecs_; } const std::vector<RtpHeaderExtension>& rtp_header_extensions() { return rtp_header_extensions_; diff --git a/media/base/testutils.cc b/media/base/testutils.cc index 8b79df4..84fd05c 100644 --- a/media/base/testutils.cc +++ b/media/base/testutils.cc @@ -29,6 +29,7 @@ #include <math.h> +#include "talk/media/base/executablehelpers.h" #include "talk/media/base/rtpdump.h" #include "talk/media/base/videocapturer.h" #include "talk/media/base/videoframe.h" @@ -255,10 +256,15 @@ void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer, // Returns the absolute path to a file in the testdata/ directory. std::string GetTestFilePath(const std::string& filename) { // Locate test data directory. +#ifdef ENABLE_WEBRTC + rtc::Pathname path = rtc::GetExecutablePath(); + EXPECT_FALSE(path.empty()); + path.AppendPathname("../../talk/"); +#else rtc::Pathname path = testing::GetTalkDirectory(); EXPECT_FALSE(path.empty()); // must be run from inside "talk" - path.AppendFolder("media"); - path.AppendFolder("testdata"); +#endif + path.AppendFolder("media/testdata/"); path.SetFilename(filename); return path.pathname(); } diff --git a/media/base/videoadapter_unittest.cc b/media/base/videoadapter_unittest.cc index af374e0..04bf3d1 100755 --- a/media/base/videoadapter_unittest.cc +++ b/media/base/videoadapter_unittest.cc @@ -69,17 +69,24 @@ class VideoAdapterTest : public testing::Test { listener_.get(), &VideoCapturerListener::OnFrameCaptured); } - void VerifyAdaptedResolution(int width, int height) { - EXPECT_TRUE(NULL != listener_->adapted_frame()); - EXPECT_EQ(static_cast<size_t>(width), - listener_->adapted_frame()->GetWidth()); - EXPECT_EQ(static_cast<size_t>(height), - listener_->adapted_frame()->GetHeight()); + virtual void TearDown() { + // Explicitly disconnect the VideoCapturer before to avoid data races + // (frames delivered to VideoCapturerListener while it's being destructed). + capturer_->SignalFrameCaptured.disconnect_all(); } protected: class VideoCapturerListener: public sigslot::has_slots<> { public: + struct Stats { + int captured_frames; + int dropped_frames; + bool last_adapt_was_no_op; + + int adapted_width; + int adapted_height; + }; + explicit VideoCapturerListener(VideoAdapter* adapter) : video_adapter_(adapter), adapted_frame_(NULL), @@ -95,6 +102,7 @@ class VideoAdapterTest : public testing::Test { EXPECT_TRUE(temp_i420.Init(captured_frame, captured_frame->width, abs(captured_frame->height))); VideoFrame* out_frame = NULL; + rtc::CritScope lock(&crit_); EXPECT_TRUE(video_adapter_->AdaptFrame(&temp_i420, &out_frame)); if (out_frame) { if (out_frame == &temp_i420) { @@ -112,12 +120,32 @@ class VideoAdapterTest : public testing::Test { ++captured_frames_; } - const VideoFrame* adapted_frame() const { return adapted_frame_; } - int captured_frames() const { return captured_frames_; } - int dropped_frames() const { return dropped_frames_; } - bool last_adapt_was_no_op() const { return last_adapt_was_no_op_; } + Stats GetStats() { + rtc::CritScope lock(&crit_); + Stats stats; + stats.captured_frames = captured_frames_; + stats.dropped_frames = dropped_frames_; + stats.last_adapt_was_no_op = last_adapt_was_no_op_; + if (adapted_frame_ != NULL) { + stats.adapted_width = static_cast<int>(adapted_frame_->GetWidth()); + stats.adapted_height = static_cast<int>(adapted_frame_->GetHeight()); + } else { + stats.adapted_width = stats.adapted_height = -1; + } + + return stats; + } + + VideoFrame* CopyAdaptedFrame() { + rtc::CritScope lock(&crit_); + if (adapted_frame_ == NULL) { + return NULL; + } + return adapted_frame_->Copy(); + } private: + rtc::CriticalSection crit_; VideoAdapter* video_adapter_; const VideoFrame* adapted_frame_; rtc::scoped_ptr<VideoFrame> copied_output_frame_; @@ -135,6 +163,13 @@ class VideoAdapterTest : public testing::Test { bool received_cpu_signal_; }; + void VerifyAdaptedResolution(const VideoCapturerListener::Stats& stats, + int width, + int height) { + EXPECT_EQ(width, stats.adapted_width); + EXPECT_EQ(height, stats.adapted_height); + } + rtc::scoped_ptr<FileVideoCapturer> capturer_; rtc::scoped_ptr<VideoAdapter> adapter_; rtc::scoped_ptr<VideoCapturerListener> listener_; @@ -157,12 +192,13 @@ TEST_F(VideoAdapterTest, AdaptInactive) { // Call Adapter with some frames. EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Do not adapt the frame rate or the resolution. Expect no frame drop and no @@ -171,13 +207,14 @@ TEST_F(VideoAdapterTest, AdaptNothing) { adapter_->SetOutputFormat(capture_format_); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); - EXPECT_TRUE(listener_->last_adapt_was_no_op()); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); + EXPECT_TRUE(stats.last_adapt_was_no_op); } TEST_F(VideoAdapterTest, AdaptZeroInterval) { @@ -187,12 +224,13 @@ TEST_F(VideoAdapterTest, AdaptZeroInterval) { adapter_->SetOutputFormat(format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no crash and that frames aren't dropped. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate at the beginning. Expect @@ -203,12 +241,13 @@ TEST_F(VideoAdapterTest, AdaptFramerate) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(listener_->captured_frames() / 2, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(stats.captured_frames / 2, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate at the beginning. Expect @@ -219,13 +258,14 @@ TEST_F(VideoAdapterTest, AdaptFramerateVariable) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 30, kWaitTimeout); + listener_->GetStats().captured_frames >= 30, kWaitTimeout); // Verify frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 30); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 30); // Verify 2 / 3 kept (20) and 1 / 3 dropped (10). - EXPECT_EQ(listener_->captured_frames() * 1 / 3, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + EXPECT_EQ(stats.captured_frames * 1 / 3, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate after capturing no less @@ -236,20 +276,20 @@ TEST_F(VideoAdapterTest, AdaptFramerateOntheFly) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop before adaptation. - EXPECT_EQ(0, listener_->dropped_frames()); + EXPECT_EQ(0, listener_->GetStats().dropped_frames); // Adapat the frame rate. request_format.interval *= 2; adapter_->SetOutputFormat(request_format); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); + listener_->GetStats().captured_frames >= 20, kWaitTimeout); // Verify frame drop after adaptation. - EXPECT_GT(listener_->dropped_frames(), 0); + EXPECT_GT(listener_->GetStats().dropped_frames, 0); } // Adapt the frame resolution to be a quarter of the capture resolution at the @@ -261,11 +301,12 @@ TEST_F(VideoAdapterTest, AdaptResolution) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and resolution change. - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(request_format.width, request_format.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, request_format.width, request_format.height); } // Adapt the frame resolution to half width. Expect resolution change. @@ -276,10 +317,10 @@ TEST_F(VideoAdapterTest, AdaptResolutionNarrow) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify resolution change. - VerifyAdaptedResolution(213, 160); + VerifyAdaptedResolution(listener_->GetStats(), 213, 160); } // Adapt the frame resolution to half height. Expect resolution change. @@ -290,10 +331,10 @@ TEST_F(VideoAdapterTest, AdaptResolutionWide) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify resolution change. - VerifyAdaptedResolution(213, 160); + VerifyAdaptedResolution(listener_->GetStats(), 213, 160); } // Adapt the frame resolution to be a quarter of the capture resolution after @@ -304,21 +345,25 @@ TEST_F(VideoAdapterTest, AdaptResolutionOnTheFly) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no resolution change before adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); // Adapt the frame resolution. request_format.width /= 2; request_format.height /= 2; adapter_->SetOutputFormat(request_format); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); - + int captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); // Verify resolution change after adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); } // Black the output frame. @@ -326,42 +371,57 @@ TEST_F(VideoAdapterTest, BlackOutput) { adapter_->SetOutputFormat(capture_format_); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify that the output frame is not black. - EXPECT_NE(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_NE(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_NE(128, *listener_->adapted_frame()->GetVPlane()); + rtc::scoped_ptr<VideoFrame> adapted_frame(listener_->CopyAdaptedFrame()); + EXPECT_NE(16, *adapted_frame->GetYPlane()); + EXPECT_NE(128, *adapted_frame->GetUPlane()); + EXPECT_NE(128, *adapted_frame->GetVPlane()); adapter_->SetBlackOutput(true); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); + int captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); // Verify that the output frame is black. - EXPECT_EQ(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetVPlane()); + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_EQ(16, *adapted_frame->GetYPlane()); + EXPECT_EQ(128, *adapted_frame->GetUPlane()); + EXPECT_EQ(128, *adapted_frame->GetVPlane()); // Verify that the elapsed time and timestamp of the black frame increase. - int64 elapsed_time = listener_->adapted_frame()->GetElapsedTime(); - int64 timestamp = listener_->adapted_frame()->GetTimeStamp(); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 22, kWaitTimeout); - EXPECT_GT(listener_->adapted_frame()->GetElapsedTime(), elapsed_time); - EXPECT_GT(listener_->adapted_frame()->GetTimeStamp(), timestamp); + int64 elapsed_time = adapted_frame->GetElapsedTime(); + int64 timestamp = adapted_frame->GetTimeStamp(); + captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); + + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_GT(adapted_frame->GetElapsedTime(), elapsed_time); + EXPECT_GT(adapted_frame->GetTimeStamp(), timestamp); // Change the output size VideoFormat request_format = capture_format_; request_format.width /= 2; request_format.height /= 2; adapter_->SetOutputFormat(request_format); + captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 40, kWaitTimeout); // Verify resolution change after adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); // Verify that the output frame is black. - EXPECT_EQ(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetVPlane()); + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_EQ(16, *adapted_frame->GetYPlane()); + EXPECT_EQ(128, *adapted_frame->GetUPlane()); + EXPECT_EQ(128, *adapted_frame->GetVPlane()); } // Drop all frames. @@ -370,11 +430,12 @@ TEST_F(VideoAdapterTest, DropAllFrames) { adapter_->SetOutputFormat(format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify all frames are dropped. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(listener_->captured_frames(), listener_->dropped_frames()); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(stats.captured_frames, stats.dropped_frames); } TEST(CoordinatedVideoAdapterTest, TestCoordinatedWithoutCpuAdaptation) { diff --git a/media/base/videoengine_unittest.h b/media/base/videoengine_unittest.h index 0f03c7b..8eab347 100644 --- a/media/base/videoengine_unittest.h +++ b/media/base/videoengine_unittest.h @@ -1593,6 +1593,25 @@ class VideoMediaChannelTest : public testing::Test, frame_count += 2; EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout); } + // Tests that adapted frames won't be upscaled to a higher resolution. + void SendsLowerResolutionOnSmallerFrames() { + cricket::VideoCodec codec = DefaultCodec(); + codec.width = 320; + codec.height = 240; + EXPECT_TRUE(SetOneCodec(codec)); + EXPECT_TRUE(SetSend(true)); + EXPECT_TRUE(channel_->SetRender(true)); + EXPECT_TRUE(channel_->SetRenderer(kDefaultReceiveSsrc, &renderer_)); + EXPECT_EQ(0, renderer_.num_rendered_frames()); + EXPECT_TRUE(SendFrame()); + EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout); + + // Check that we send smaller frames at the new resolution. + EXPECT_TRUE(rtc::Thread::Current()->ProcessMessages(33)); + EXPECT_TRUE(video_capturer_->CaptureCustomFrame( + codec.width / 2, codec.height / 2, cricket::FOURCC_I420)); + EXPECT_FRAME_WAIT(2, codec.width / 2, codec.height / 2, kTimeout); + } // Tests that we can set the send stream format properly. void SetSendStreamFormat() { cricket::VideoCodec codec(DefaultCodec()); diff --git a/media/base/videoframe.cc b/media/base/videoframe.cc index 1c5cfd8..018d065 100644 --- a/media/base/videoframe.cc +++ b/media/base/videoframe.cc @@ -235,7 +235,7 @@ bool VideoFrame::SetToBlack() { } static const size_t kMaxSampleSize = 1000000000u; -// Returns whether a sample is valid +// Returns whether a sample is valid. bool VideoFrame::Validate(uint32 fourcc, int w, int h, const uint8 *sample, size_t sample_size) { if (h < 0) { @@ -311,6 +311,11 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " " << sample_size; return false; } + // TODO(fbarchard): Make function to dump information about frames. + uint8 four_samples[4] = { 0, 0, 0, 0 }; + for (size_t i = 0; i < ARRAY_SIZE(four_samples) && i < sample_size; ++i) { + four_samples[i] = sample[i]; + } if (sample_size < expected_size) { LOG(LS_ERROR) << "Size field is too small." << " format: " << GetFourccName(format) @@ -318,10 +323,10 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " " << sample_size << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); return false; } if (sample_size > kMaxSampleSize) { @@ -331,13 +336,14 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " " << sample_size << " expected: " << 2 * expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); return false; } // Show large size warning once every 100 frames. + // TODO(fbarchard): Make frame counter atomic for thread safety. static int large_warn100 = 0; size_t large_expected_size = expected_size * 2; if (expected_bpp >= 8 && @@ -350,27 +356,14 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " bytes: " << sample_size << " expected: " << large_expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); - } - // Scan pages to ensure they are there and don't contain a single value and - // to generate an error. - if (!memcmp(sample + sample_size - 8, sample + sample_size - 4, 4) && - !memcmp(sample, sample + 4, sample_size - 4)) { - LOG(LS_WARNING) << "Duplicate value for all pixels." - << " format: " << GetFourccName(format) - << " bpp: " << expected_bpp - << " size: " << w << "x" << h - << " bytes: " << sample_size - << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); } + // TODO(fbarchard): Add duplicate pixel check. + // TODO(fbarchard): Use frame counter atomic for thread safety. static bool valid_once = true; if (valid_once) { valid_once = false; @@ -380,10 +373,10 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " bytes: " << sample_size << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); } return true; } diff --git a/media/base/videoframe_unittest.h b/media/base/videoframe_unittest.h index c4a7a8c..483fc34 100644 --- a/media/base/videoframe_unittest.h +++ b/media/base/videoframe_unittest.h @@ -135,6 +135,9 @@ class VideoFrameTest : public testing::Test { rtc::scoped_ptr<rtc::FileStream> fs( rtc::Filesystem::OpenFile(path, "rb")); if (!fs.get()) { + LOG(LS_ERROR) << "Could not open test file path: " << path.pathname() + << " from current dir " + << rtc::Filesystem::GetCurrentDirectory().pathname(); return NULL; } @@ -143,6 +146,7 @@ class VideoFrameTest : public testing::Test { new rtc::MemoryStream()); rtc::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get()); if (res != rtc::SR_SUCCESS) { + LOG(LS_ERROR) << "Could not load test file path: " << path.pathname(); return NULL; } @@ -419,17 +423,22 @@ class VideoFrameTest : public testing::Test { const uint8* u, uint32 upitch, const uint8* v, uint32 vpitch, int max_error) { - return IsSize(frame, width, height) && + return IsSize(frame, + static_cast<uint32>(width), + static_cast<uint32>(height)) && frame.GetPixelWidth() == pixel_width && frame.GetPixelHeight() == pixel_height && frame.GetElapsedTime() == elapsed_time && frame.GetTimeStamp() == time_stamp && IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch, - width, height, max_error) && + static_cast<uint32>(width), + static_cast<uint32>(height), max_error) && IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch, - (width + 1) / 2, (height + 1) / 2, max_error) && + static_cast<uint32>((width + 1) / 2), + static_cast<uint32>((height + 1) / 2), max_error) && IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch, - (width + 1) / 2, (height + 1) / 2, max_error); + static_cast<uint32>((width + 1) / 2), + static_cast<uint32>((height + 1) / 2), max_error); } static bool IsEqual(const cricket::VideoFrame& frame1, @@ -719,7 +728,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBP, @@ -735,7 +744,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBO, @@ -751,7 +760,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_R444, @@ -771,12 +780,12 @@ class VideoFrameTest : public testing::Test { size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment); \ T frame1, frame2; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ ASSERT_TRUE(ms.get() != NULL); \ - libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8 *>(ms->GetBuffer()), \ + libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8* >(ms->GetBuffer()), \ kWidth * 4, \ bayer, kWidth, \ kWidth, kHeight); \ @@ -812,8 +821,8 @@ void Construct##FOURCC##Mirror() { \ reinterpret_cast<uint8*>(ms->GetBuffer()), \ data_size, \ 1, 1, 0, 0, 0)); \ - int width_rotate = frame1.GetWidth(); \ - int height_rotate = frame1.GetHeight(); \ + int width_rotate = static_cast<int>(frame1.GetWidth()); \ + int height_rotate = static_cast<int>(frame1.GetHeight()); \ EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0)); \ libyuv::I420Mirror(frame2.GetYPlane(), frame2.GetYPitch(), \ frame2.GetUPlane(), frame2.GetUPitch(), \ @@ -845,8 +854,8 @@ void Construct##FOURCC##Rotate##ROTATE() { \ reinterpret_cast<uint8*>(ms->GetBuffer()), \ data_size, \ 1, 1, 0, 0, 0)); \ - int width_rotate = frame1.GetWidth(); \ - int height_rotate = frame1.GetHeight(); \ + int width_rotate = static_cast<int>(frame1.GetWidth()); \ + int height_rotate = static_cast<int>(frame1.GetHeight()); \ EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0)); \ libyuv::I420Rotate(frame2.GetYPlane(), frame2.GetYPitch(), \ frame2.GetUPlane(), frame2.GetUPitch(), \ @@ -995,7 +1004,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ // Convert back to ARGB. size_t out_size = 4; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB, out, @@ -1032,7 +1041,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ // Convert back to ARGB size_t out_size = 10 * 4; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB, out, @@ -1431,8 +1440,8 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t out_size = astride * kHeight; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment + 1]); memset(outbuf.get(), 0, out_size + kAlignment + 1); - uint8 *outtop = ALIGNP(outbuf.get(), kAlignment); - uint8 *out = outtop; + uint8* outtop = ALIGNP(outbuf.get(), kAlignment); + uint8* out = outtop; int stride = astride; if (invert) { out += (kHeight - 1) * stride; // Point to last row. @@ -1869,7 +1878,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment); \ T frame; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ @@ -1898,7 +1907,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + 1 + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment) + 1; \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment) + 1; \ T frame; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ @@ -1935,7 +1944,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer1 = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer1 = ALIGNP(bayerbuf.get(), kAlignment); \ for (int i = 0; i < kWidth * kHeight; ++i) { \ bayer1[i] = static_cast<uint8>(i * 33u + 183u); \ } \ @@ -1951,7 +1960,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ } \ rtc::scoped_ptr<uint8[]> bayer2buf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer2 = ALIGNP(bayer2buf.get(), kAlignment); \ + uint8* bayer2 = ALIGNP(bayer2buf.get(), kAlignment); \ libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8*>(ms->GetBuffer()), \ kWidth * 4, \ bayer2, kWidth, \ diff --git a/media/devices/linuxdevicemanager.cc b/media/devices/linuxdevicemanager.cc index a79e226..d122169 100644 --- a/media/devices/linuxdevicemanager.cc +++ b/media/devices/linuxdevicemanager.cc @@ -31,10 +31,10 @@ #include "talk/media/base/mediacommon.h" #include "talk/media/devices/libudevsymboltable.h" #include "talk/media/devices/v4llookup.h" -#include "talk/sound/platformsoundsystem.h" -#include "talk/sound/platformsoundsystemfactory.h" -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundsysteminterface.h" +#include "webrtc/sound/platformsoundsystem.h" +#include "webrtc/sound/platformsoundsystemfactory.h" +#include "webrtc/sound/sounddevicelocator.h" +#include "webrtc/sound/soundsysteminterface.h" #include "webrtc/base/fileutils.h" #include "webrtc/base/linux.h" #include "webrtc/base/logging.h" @@ -89,7 +89,7 @@ static const char* kFilteredVideoDevicesName[] = { }; LinuxDeviceManager::LinuxDeviceManager() - : sound_system_(new PlatformSoundSystemFactory()) { + : sound_system_(new rtc::PlatformSoundSystemFactory()) { set_watcher(new LinuxDeviceWatcher(this)); } @@ -102,7 +102,7 @@ bool LinuxDeviceManager::GetAudioDevices(bool input, if (!sound_system_.get()) { return false; } - SoundSystemInterface::SoundDeviceLocatorList list; + rtc::SoundSystemInterface::SoundDeviceLocatorList list; bool success; if (input) { success = sound_system_->EnumerateCaptureDevices(&list); @@ -118,12 +118,12 @@ bool LinuxDeviceManager::GetAudioDevices(bool input, // device at index 0, but Enumerate(Capture|Playback)Devices does not include // a locator for the default device. int index = 1; - for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin(); + for (rtc::SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin(); i != list.end(); ++i, ++index) { devs->push_back(Device((*i)->name(), index)); } - SoundSystemInterface::ClearSoundDeviceLocatorList(&list); + rtc::SoundSystemInterface::ClearSoundDeviceLocatorList(&list); sound_system_.release(); return FilterDevices(devs, kFilteredAudioDevicesName); } diff --git a/media/devices/linuxdevicemanager.h b/media/devices/linuxdevicemanager.h index 88aee4e..1eb648f 100644 --- a/media/devices/linuxdevicemanager.h +++ b/media/devices/linuxdevicemanager.h @@ -32,7 +32,7 @@ #include <vector> #include "talk/media/devices/devicemanager.h" -#include "talk/sound/soundsystemfactory.h" +#include "webrtc/sound/soundsystemfactory.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/stringencode.h" @@ -47,7 +47,7 @@ class LinuxDeviceManager : public DeviceManager { private: virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); - SoundSystemHandle sound_system_; + rtc::SoundSystemHandle sound_system_; }; } // namespace cricket diff --git a/media/devices/macdevicemanager.cc b/media/devices/macdevicemanager.cc index 568ee53..8f777b4 100644 --- a/media/devices/macdevicemanager.cc +++ b/media/devices/macdevicemanager.cc @@ -71,7 +71,7 @@ static const UInt32 kAudioDeviceNameLength = 64; extern DeviceWatcherImpl* CreateDeviceWatcherCallback( DeviceManagerInterface* dm); extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl); -extern bool GetQTKitVideoDevices(std::vector<Device>* out); +extern bool GetAVFoundationVideoDevices(std::vector<Device>* out); static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out); static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out); @@ -84,7 +84,7 @@ MacDeviceManager::~MacDeviceManager() { bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { devices->clear(); - if (!GetQTKitVideoDevices(devices)) { + if (!GetAVFoundationVideoDevices(devices)) { return false; } return FilterDevices(devices, kFilteredVideoDevicesName); diff --git a/media/devices/macdevicemanagermm.mm b/media/devices/macdevicemanagermm.mm index 3091ec4..cfcf5a4 100644 --- a/media/devices/macdevicemanagermm.mm +++ b/media/devices/macdevicemanagermm.mm @@ -33,6 +33,11 @@ #include "talk/media/devices/devicemanager.h" #import <assert.h> +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + #import <AVFoundation/AVFoundation.h> +#endif +#endif #import <QTKit/QTKit.h> #include "webrtc/base/logging.h" @@ -136,4 +141,52 @@ bool GetQTKitVideoDevices(std::vector<Device>* devices) { return true; } +bool GetAVFoundationVideoDevices(std::vector<Device>* devices) { +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + if (![AVCaptureDevice class]) { + // Fallback to using QTKit if AVFoundation is not available + return GetQTKitVideoDevices(devices); + } +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; +#else + @autoreleasepool +#endif + { + NSArray* capture_devices = [AVCaptureDevice devices]; + LOG(LS_INFO) << [capture_devices count] << " capture device(s) found:"; + for (AVCaptureDevice* capture_device in capture_devices) { + if ([capture_device hasMediaType:AVMediaTypeVideo] || + [capture_device hasMediaType:AVMediaTypeMuxed]) { + static NSString* const kFormat = @"localizedName: \"%@\", " + @"modelID: \"%@\", uniqueID \"%@\", isConnected: %d, " + @"isInUseByAnotherApplication: %d"; + NSString* info = [NSString + stringWithFormat:kFormat, + [capture_device localizedName], + [capture_device modelID], + [capture_device uniqueID], + [capture_device isConnected], + [capture_device isInUseByAnotherApplication]]; + LOG(LS_INFO) << [info UTF8String]; + + std::string name([[capture_device localizedName] UTF8String]); + devices->push_back( + Device(name, [[capture_device uniqueID] UTF8String])); + } + } + } +#if !__has_feature(objc_arc) + [pool drain]; +#endif + return true; +#else // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 +#else // __MAC_OS_X_VERSION_MAX_ALLOWED + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED +} + } // namespace cricket diff --git a/media/other/linphonemediaengine.h b/media/other/linphonemediaengine.h index e4ba345..b4950de 100644 --- a/media/other/linphonemediaengine.h +++ b/media/other/linphonemediaengine.h @@ -70,7 +70,6 @@ class LinphoneMediaEngine : public MediaEngineInterface { virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch); virtual SoundclipMedia* CreateSoundclip() { return NULL; } virtual bool SetAudioOptions(int options) { return true; } - virtual bool SetVideoOptions(int options) { return true; } virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return true; } @@ -81,7 +80,6 @@ class LinphoneMediaEngine : public MediaEngineInterface { virtual bool SetOutputVolume(int level) { return true; } virtual int GetInputLevel() { return 0; } virtual bool SetLocalMonitor(bool enable) { return true; } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; } // TODO: control channel send? virtual bool SetVideoCapture(bool capture) { return true; } virtual const std::vector<AudioCodec>& audio_codecs() { diff --git a/media/sctp/sctpdataengine_unittest.cc b/media/sctp/sctpdataengine_unittest.cc index 0fb9b9d..fb00650 100644 --- a/media/sctp/sctpdataengine_unittest.cc +++ b/media/sctp/sctpdataengine_unittest.cc @@ -45,6 +45,11 @@ #include "webrtc/base/ssladapter.h" #include "webrtc/base/thread.h" +#ifdef HAVE_NSS_SSL_H +// TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. +#include "webrtc/base/nssstreamadapter.h" +#endif // HAVE_NSS_SSL_H + enum { MSG_PACKET = 1, }; @@ -69,8 +74,7 @@ class SctpFakeNetworkInterface : public cricket::MediaChannel::NetworkInterface, // TODO(ldixon): Can/should we use Buffer.TransferTo here? // Note: this assignment does a deep copy of data from packet. - rtc::Buffer* buffer = new rtc::Buffer(packet->data(), - packet->length()); + rtc::Buffer* buffer = new rtc::Buffer(packet->data(), packet->length()); thread_->Post(this, MSG_PACKET, rtc::WrapMessageData(buffer)); LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::SendPacket, Posted message."; return true; @@ -219,6 +223,12 @@ class SctpDataMediaChannelTest : public testing::Test, // usrsctp uses the NSS random number generator on non-Android platforms, // so we need to initialize SSL. static void SetUpTestCase() { +#ifdef HAVE_NSS_SSL_H + // TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. + if (!rtc::NSSContext::InitializeSSL(NULL)) { + LOG(LS_WARNING) << "Unabled to initialize NSS."; + } +#endif // HAVE_NSS_SSL_H rtc::InitializeSSL(); } @@ -266,6 +276,10 @@ class SctpDataMediaChannelTest : public testing::Test, virtual void TearDown() { channel1()->SetSend(false); channel2()->SetSend(false); + + // Process messages until idle to prevent a sent packet from being dropped + // and causing memory leaks (not being deleted by the receiver). + ProcessMessagesUntilIdle(); } void AddStream(int ssrc) { diff --git a/media/testdata/faces.1280x720_P420.yuv b/media/testdata/faces.1280x720_P420.yuv new file mode 100644 index 0000000..f4844ee --- /dev/null +++ b/media/testdata/faces.1280x720_P420.yuv @@ -0,0 +1 @@ +IIIIIIIIIHHHHIIIIJJJJJIIIIIIJJJJJJJJJIIJJJJJJJJJJJJIIIJJJJJJKKLLMMMMMMLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNNOOOOOOOOOPPOOPPPPPPPPOPQQQQQQQPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQQRRSSSTTTTUUTTTTTTTUUUUUUUUUUUUUUUUUUVVVVVVVVVVUUVVVVVVVVVVVWWWWWWWWWWWWWXXXXXXXXXXXXXYYXXXWWWXXYZZYYYYYYZZZZZZYYYZ[[[[[[ZZZ[\[ZZZ[[[\\\\\\\[\\]\\[\[[]^]]\\\\\\[[\\]\\[\\[[\]]\[[\\]^^\[\]^_____________`aa```````_____```aaaaaa``ba`abca__`bccdbbba[O@3--143.' "(*' !#!"" "! !#$$% %*)$"#&++*1>MZbggfgfbbceeddddcceeeeeeddcccbbccccccccccccdddddddddddddeeeeeeeeeeeeeddddddddddddcccccccccccccccccbbbbbbbbbcdddddddddddddcccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaabbaa`____```````aaaa``___`````````````^^^^^^^^^^^]]]]]]]]]]]]]]^^^]]]\\\[[[[[[[\\\]\\[\[[[\\\[[[[ZZZZZZZZZZZZ[[[[[ZYYYYYYYYYYYXXYXXXXXXXXXXWXXXXXWWWWVVVVWWWWWWWVVVVVVUVVVVVVVVVVVUUTUUVVVVVUUUUUUUUUTTTTTTTSSSSSRRRRRRRRSTTSSSSRRRQQQQQQQQQQQQQQQQQQQQQQQPPPPPPOOOOONNNNNNNMMNNNNNNNNNMMLLLLLLLLLLKKKMMMMMMLLLLLLLLLLLLLKKKKKKKJJJJJJJJJJJIJJJJIIIIIIIJIIIIIIIHHHHHHHHGGFFFFFFFFGGFFFFFGFFFEEEFFEEEEEEEEEEDEEEEEEEDDDDDDEEDDDDDDDDDEDBBCCCCCBBBBAACBBBBBBBBBBAABBBA@@@@@@@@???????????????@@@?>==>>??>=>>>>>>>====>=========<<<<<<<<<<<<<<<<<<<<<;;;;;;;<<<;;;;<<<<;;IIIIIIIIIHHHHIIIIJIIIIIIIIIIIIIIIIIIIJJJJJJJJJJJJKKIIJJKKJJJJKLLMMMMMMLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNOONNNNOOOOOOOOPPPPPPPOOPQQQQQQQQQQPPPQQQQQQQQQQQQQRQRRRRRRRRSRRRRRRRSSSSTTTTUUTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXXYYXXXXWXXYYZZYYYYYYZZZZZZYYYZ[[[[[[ZZZ[[[ZZZ[[[\[[[[[[[[\\\[[[[[\]]\\\\\\\\\\\\\\\[[\\\\\\\\\]]^^]\\]^_____________`aa``````______```aaaaaa`_^_```_`abbbaaaa`^YN?1&"#(++'"#(*)% ""! !"#$ %('""'+*(+2<FQ]ehfeddddeedccdfeeeeeeeddccccdddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddccccccccccdddddddddddddccccccccccccccbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaa````_```````aaaa`_____````````````_^^^^^^^^^^^^]]]]]]]]]]]]^^^^]]]\\[[[[[[[[[\\\\[[[[[[[[[[[ZZZZZZZZZZZZZ[[[[[ZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWVVVWWWWWWWVVVVVVVVVVWWWWVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTSSSSSSSRRRRRSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQPPPPPPONNNNNNNNNNNNMMNNNNNNNNNMMMMLLLLLLLLLLLMMMMMMLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIJIIHHHHIHHHHHHHGGGGFFFFFFFFFFFFFGGGFFFEEEFEEEEEEEEEEDEEEEEEEDDDDDDEEEDDDDDDDDDDBBCCCCCCBBBABCBBBBBBBBBAAABBAAA@@@@@@@@@@@@@@@@@@@@??@@@?>==>>??=========>>>>=====<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;:::;;;;;::JJJJIIJIIIIIIIHIIIIIIIIHHIIIIIIIIIIJJJJJJJJJJJJJJKKJJJKLKKJJKKLLMMMMMMLLLLLLLMMMMMMMMMMNNMMMMNNNNNNNNNNOOOOOOOOPPOOOPPPPPPPPPPOPQQQQQQQQQQPPQQQQQQQQQQQQQQQQQRRRRRRSSSSRRRRSSSTTTTTTTTTSTTTTUUUUTTTTTTTTTTTTUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWXXXXXXXXXXXXXYXXXXXXXYYYYYYYYYYYZZZZZZYYYZZZZZZZ[ZZ[[[ZZZZZ[\[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]^^^]]\]^_____________`a````````_____````aaaaa`_]_a_]]acb]XVTSUVSK=.""#"'+)$!!!!!"! !! "#$$"!')'#""%,8IV^`bfgedefecbdfeeeeeeeeddccdddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddeeededccccdddddddddddddddddcccccccccccccbbbbbbbbbbbbaaabaaaaaa```aaaaaaaaaaaa`````aaaa``_```aaaaaa```__`````````````___^^^^^^^^^^]]]]]]]]]]]]^^^^]]]]\[[\\\\\\\[[[[[[[[[[[[[[[[[[[[[ZZZZ[[ZZZZZ[ZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWVVVWWWWWWWVVVVVVVVVVVWWWVVVVVUUUVVVVVVVUUUTTTTUTTTTTTTTTTTTTTSSSSSSSRRRSSSRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPOOOOOOOONNNNNNNNOONNONNNNNNMMMMMMMMMMLMMMMMMLLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJIJJJJJIIIIIIIIIIIIHHHHGGGGGFFFFFFFFFGGGGGGGFFFEFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEDDDDDDDDDCCCCCCBBCBBBBCBBBBBBBBBAAABBAAAA@@@@@@@@@@@@@@@@@@@??@@@?>==>>??=<<=====>>>>>===>====<<===============<<<<<<<<<<;;;;::::::;;::::JJJJIIIIIIIIIHHIIIIIIIIHHIIIIIIIIIIJJJJJJJJJJJJJKKKJJKLLKKKKKKLLLLLMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNOOOONNNOOOOOOOOOOPPPPPOOOPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQRRRRSSSTSSSSSSSTTTTTTTTTTTTTUTUUVUUTUUUUUUUUUTUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYZZZZZZZZZZZZZ[[[[[Z[[[[ZZZ[[[[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\]]]\\\\\\]]]]]]]^__^^]]^______________```````````````````aaaaaa``bc_]^dc\OEA=;ADD<1(! "$!!"&&"! !#" ! !"#!#&$!&4>GR]dgfcdggdbbceeeeeeeeeeeddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddeeeeeeeeeeeeefeeeeddddeeeedddddddddddddddddcccccbbbbbbbbbbbbbbbbbaaaaaaaaa``aaaaaaaaaaaaa```a`aaaaa````aaaaaaaaa````````````````_____^^^^^^^^^^^^^^^^^^^^^_^^^]]]]\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXWWWWWVWWWWWWWWVVVVVVVVVVWWWVVVVVUUVVVVVVVVUUUTTTTTTUTTTTTTUUUUUUUUTTTTRRRRRSSSSSSSSRRRRRRRRRRRRRQQQQQQQQPPPPPOOOOOOONNOOONNNONNNNNNONNNOONNNNNNNNNNMMMMMMMMLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIHHHHIIHGGGGGGGGGFFFFGGGGGGGGGFFFFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEEDDDDDDDDCCCCCBBBBBCCCBBBBBBBBBBBAABBBBBBAAA@@@@@@@@@@@@@@@@??@@@?>==>>??=<<<====>>>>>========<<<<<<<<<<<<<<<<<=====<<<<<<<<;::::::;;;:::JJJIIIIIIIIIIHIIIIIIIIIIHIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLMLKKKKLLLLLLMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOPPPPPQQPPPPOOPPPQQQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQQRRSSSTTTTTTSSSTSTTTTTTTTTTTTUUUVVUUUUUUUUUUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZYYZZZZZZ[[[[Z[[[[[ZZZ[Z[\\[\\\\[\\\\[\\\\\\\\\\]]]]]]]^]]]]]]]]]]^^]]]^___^^]^_______________`````````````````aaaa```accb_adf^N</+&$)--*$ !" "'(%! $# !$$! ! "$$#$#%(-9HU_dccdffdcdeeeeeeeffffeefffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeedddddeeeeeeeeeeeeefffeeeeeeeeeedddddddddddddeeddddccbbbbbbbbbbbbbbbbbbaaaabbaaa``aaaaaaaaaaaaaa````abaaaa``aaaaaaa`aa````_```````````________^^^^^_______________^^^^^]]]]]]^]]\\[\\\\[[\\\\\\\\\\\\\[Z[[[[[[[[[[ZZZZZYYYYYYYYYZZZZYYYYYYYYYYYXXXXXWWWWWWWWWWWWWWWVVVVVVWWWWWWWVVVVVVVVVVVVVVVUTTTTTTTTTTSTTUUUTUUTTTTUTSRSRSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQPPPPPPPPPOOOOOOOONNOOOOOOONNNOOOONNNNNNNNNMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKJJJJJJJJJJKKKKKKJIIIIIIIIIIIIIIIHGGGGHGHGGGGFFGGGGGGHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEEEEEDDDDDDDDCCCCCCCCCCCBBBBBBBBBBAABABBBBAAAA@@@@@@@@@@@@@@@??@@@?>==>>??=<<=====>>>>>>>========================<<<<<<<<<<;;;;;;::;;;;;;;IIIIIIIIIIHHHIIJIIJJJIJIIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLMMMLKKKLLLLLLMMMMMMMMMNOOOONNNNNNNNNNNNOOOOOOOOOOOOOOOPPPPPPPQQPPPOOOPPPQQQQQQQQQQQQRRRRSRRRRRRRRRSRQQQRRRSSSTTTTTTTTTTTTTUUUTTTTUUUUUVVVVUUUUUUUUUUUUUVVVVUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZ[[[[[[[Z[[[[\[[ZZZ[[[[[[[\\[[[[[[\\\]]\\\\\\]]]]]]]^^]]]]]]]]^^^]]^^_____^^^_____________``````````````````````a``ab`_`bc^O=, !!!"! "#" %(+*"#$""$"!! !!$%%$"""!%0@Q_efedccddeeeeeeefffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeededdeeeeeeeeeeeeefeeeeeeeeeeeeeeeeeeeeeeeeeeeedddccbbbbcccccccccccccaaabbbbbba``aaaaaaaaaaaaaaaaa`aaaaaaaaabbabbaa```aa`````````````_________^^^_________________^^^^^^]]]^^]]]\\[[[[\\\\\\\\\\\\\\\[[[[Z[[[[[[ZZZZZZZYYYYYYYYZZZZZYYYYYYYYYYYXXXXXWWWWWXXWWWWWWWWWWVVVWWWWWWWVVVVVVVVVVVVVVVVUUUUTUUUUTTTTTTTUUUUTUUTTTSSSSSSSRRRRRRRRRRRRRRRRRRQRQQQQQQQQQQPPPPPPPPPOOOOOOOOOOOOOOOOONNNNNNNNMMMMMMMMMMMMMMMLLLLMLLLLLLLLKLKKKKKKKJJJJJJJJJKKKKKKJIIIJJJJIJJJJIIIIGGGHHHHHGGGFFGHHHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDEEEEEEEEDDDDEEDDDCCCCDDDCCBBBBBBBBBAAABBBCCCBBBA@@@@@@@@@@@@@@@??@@@?>==>>??>===========>>>===========================<====<<<<<<<;;;;<<<<;;IIIIHHHHHHHHIIIIIJJJJIIIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLLLMMMLKKLLMLLMMMMMMMMNNNNNNNNNNONNNONNNONNNNNNOOOOOOOOOPPPPPPPQQPPPPOPPPPQQPPQQQQQQQRRRRSSSSSSSSSSSSRQQRRRRSSTTTTTTTSTTUUUUUUUTTTUUUVVVVVVVVUVVVVVVVVVVUUUUUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYXXYYYYYYYYYYYZZZZZZZZZZ[[ZZZZZZZ[[[[\\[[[ZZZ[Z[\\[\\\\[\[[\\\\]]]\\\\\]]]]]]]^^^]]]]]]^^^^^^^_____`__^_```````````````````````````````_``aaa`___a`YL;+! ! #$#" " !'+*)& "! !!" ""! !$$! +>Tdjhdabdeeeeeeeefffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeffffffffffffffeeeddddddeeeeeeeeeeeeeeeeeeeeddcccbbbcccccccccccccbabbbbbbbaa`abbbbbbbbbbbbbbbaa`a`aaaabbaaaaaaa`aaaaaaaaaaaaaaaa````___________________________^^^^^^]]^^]]]]\\\\\\\\\\\\\\\\\\\\\[[[[ZZZZ[ZZZZZZZZZYYYYYYYZZZZZZYYYYYYYYYYYXXXXXWWWXXXXWWWWWWWWWVVVWWWWWWWVVVVVVVVVVVVVVVVUUUUTUUUUUUUTSSTUTTUUUUTTTTTTTSSSRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQPPPPPPPPOOOOONNOOOOOOOONNNMNNNMMMMMMMMNNNMMMMMMMMMMMMMLLLLLLLLLLKKKKKKJJJJJJJJJKKKKKKJJIIJJJJIIIIIIIIIHHHHHHHHHGGGGHHHHHHHGGGGGFFEEEEEEEEEEDEEEEEEEDDDDDDDDEEEEEEEEDCCEEDDDDCCCDDDDCBABBBBBBBAAABBCBBBBBAA@@@@@@@@@@@@@@@??@@@?>===>??>>>>======<<=>>=========<=================<<<<<<==<<;;;;;;<<<;;;HIHHHHHHHHHGHIIIIJJJJIIIIIIIIIJJJJJJJJJJJJJKKKKKKKKKKLLMMLLLLLLMMMMMMMMMMMNNNNNNNNNNONNNONNNONNNNNNOOOPOOOOOPPPPPPPQQPPPPPPPPPQQPPPQQQQQRRRRRRRRRRRRRRRRRRQRRRRSSSTTTTTTTTTTUTTTTUUUTTUUUVVVVVVUVVVVVVVVVVVVVUUUUVVVVVVVVVVVVVWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZ[[[[[[[[Z[[[[[[\[[[ZZZ[[[[[[[[[[[[[\\\\]]]]]]]]]]]]]]]]^^^^^]]]^^^^^^___^^__``_^_``````````````````````````````````aaa`_abaXH6& !#""*/.'!!$" !"#""! *AZhkgdcdeeeeeeeeeffffggfggggggggggffffffffffffffffffffffffffffffffeeeeeeeffffffffffffffeeedddddddeeeeeeeeeeeeeeefeeeddcccbbbcccccccccccccbbbbbbbaabaaabbbbbbbbbbbbbbbbbaaaaaabbbaabaaaaaaaaaaaaaaaaaaaaa```````________________________^^^^^^^^^^]]]\\\\\\\\]]\\\\\\\\\\\[[[[[[Z[[ZZZZZZZZZZZYYYYYYZZZZZZYYYYYYYZYYYYYXXXXXXXXXXXWWWWWWWWVWWWWWWWWWWVVVVVVVVVVVVVVVUUUUUTTTTTUTSSSTTTUUUTTTTTUTTTTSSRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQPPPPPPPPOOOOONNOOOOOPOONNNNNNNMMMMMMMMNNNNMMMMMMMMMMMMMLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJIIJJIIIIIIIIIIHHGHHHHHHHHHGGHHHHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEEEFFFEEDCCEEDDDDDCCCCDDCBBBBBBBBBAAABCBBBBBBBAAA@@@@@@@@@@@@@??@@@?>===>??>>>>>===<<<<==========<<<================<<<<<<<=<<;::::::;;;:::IIIIIHHHHHHHHIIIJJJJJJJIIIIIIJJJJJJJJJJJJJKKKKKKKKKKKLLLLLLLLMMMMMMMMMMMMMMMMMMMMMNNOONOONNNNNNOOOOOOOOOOOOOOPPPPPPPPPQQQQQQQPPPPPPQQQQRRRRRSRRRRRRRRRRRRRQRRSSSSSSTTTSSTTTTTTTUUUUUUUUVVVVVVVUVVVVVVVVVVVVVVVUUVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZ[[[[ZZZ[Z[[[[[[[[[[[ZZZ[[[[[[[[[[[[\\\]]]]]]]]]]]]]]]]]]^^^^]]]]]^^^^^___^^__``_]^````````````___``````````````````aaaabcdaXH5% #)//) #))% "#$$" "" ! #&% ,DW`bbcfgeeeeeeeeeffffggggggggggggfffffffffffffffffffffffffffffffffeeeeeefffffffffffffffeeeeddeeeeeeeeeeeeeeeeeeeeeedddccccccccccccccccccbbbbbbaaaaaaabbbbbbbbbbbbbbbbbaaaaaabbbaaaaa`aaaaaaaaaaaaaaaaaaa``````_______________________^^^^^^^^^^]]]]]]]]]]\\]]]]]\\\\\\[\\\[[[[[[[ZZZZZZZZZZZZYYYYYZZZZYYYYYYYYYZZYYYYYXXXXXXXXXXXWWWWWWWVVWWWWWWWWWVVVVVVVVVVVVVVVUUVVUUUTTTUUSSSTUUUUUUUTTTTTTTTSSRSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPPPOOOOONOOOOOOOOOOONMNNNMMMMMMMMMNNNNMMMMMMMMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJIJJJJJIIIJJIIIIIIIIIIHHHHHHHHHHHHGGGGGHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDEEEFFFFEEDDDEEDDDDDDCCCDCBBBBBBBBBBAAACCBBBAAAAAAA@@@@@@@@@@@@@??@@@?>==>>??>>>>======<<=========<<<================<<<<==<<<<<;::::::;;::::JJJJIIIIIIIIIIIIJJJJJJJIIIIIJJJJJJJJJKJJKKKKKKKKKKKKKKLLLLLLLMNNNNNNNMMMMMMMLLLLLMNNNOOOPONNNNNNNOOONNOOOOOOOOPPPPPPPPQQQQQQQPPPPPPQQQRRRRRSSRRRRRRRRRRRRRRRRSSSSSSSSSSSSSTTTTUUUUUUVUVVVVVVVUUVVVVVVVVVVVVVVUUVVVVVVVVVWVVVWWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYZZYYYXXXYYZ[[ZYZZYZZ[ZZZ[ZYZ[[[\\\[[[Z[[[ZZZ[Z[[[[[[[[\\\\]]]]]]]]]]]]]]]]]]]^^^]]]]]]^^^^^^__^^_`a`_]^````````````____``````aaaa````__```aabdbZJ6% #).1,"!*.,$"#""!! "'& -<HOV]dhfeeeeeddeeeefggggggggggggffffffffffffffffffffffffffffffffffffeeeffffffffffffffffeeeddeeeeeeeeeeeeeeeeeeeeeedddcccccccccccccccccccbbbbbaaaaabbbbbbbbbbbbbbbbbbcbbbbaaaaaaaa```````aaaaaaaaaaaaaaa``````````________________^_^^^^^^^^^^]]]]]]^^]]]]]]]]]]\\\\\[[[\\\[[[[ZZZZZZZZZZZZZZZYYYYZYYYYYYYYYYXYZZZYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVUUUUUUVVVVUUUUUUUTTTTTTSTTUVVVVVUUUTSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPPPOOOOONNNNOOOONOOONNNNNNMMMMMMMNNNNNNMMMMMMMMMMMMMLLLLLLLKKKKKKKKKKJJJJJJIIIIIIIIIIIIIIIIIHHHHIIIHIHHGHHHHHHGFGGHHGGGGFFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEFFFFFEEDDCDDDDDDDDDDCCCBBBBBBBBBBAAABCBBAAAAAAAA@@@@@@@@@@@@@??@@@?>===>??>>>===============<<<<<<<<==========>==<<<<<<<<;;<<;;;:::;;;;;::JJJIIIIIIIIIIIIIJJJJJJJIIIIJJJJJJJJJKKKJKKKKKKKKKKLKJKLLLLLLLMMMMNNNNNMMMMMMMLLLMMNNNNOOOONNNNNNNONNNNNOOOOOOOOOOPPPPPQQQQQQQPPPPPPQQQRRRRSSSRRRRRRRRRRRRRSSSSSSSSSSSSSSSSTTTTUUUUUUUUVVVVVVVUUVVVVVVVVVVVVVVVVVVVVVVVVVWWVWWWWWWWWWWXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZYYXXXYYZZZZZZZZZZ[[[[[ZZZ[[\\\\[[ZZ[[[ZZZZZ[[[\\[[[\\\]]]]]]]^^^]]]]]]]\\]^^^]]]]]]^^^^]^_^^^_```_]^````````````____`_``````aa`__````_``acc\M:&! !!!#&,/.&!(..( !#"$%$,5AMYbefeeddddeeefffggggfffggggffffffffffffffffffffffffffffffffffffffffffffffffffffgfffeeeeeeefeeeeeeeeeeeeeeeeeedddccccccccccccccccccccbbbaaaaaabbbbbbbbbbbbbbbbbccccbbaa``abba```````a``````````````a```````````_____________^^^^^^^^^^^^^]\]]]^^^^]]]]]]]]]\\\\[[[[\\\\[[[Z[ZZZZZZZZZZZZZZZYYZYYYYYYYYYYXYZZZZYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWVVVVUVVUUVVVVUUUUUUUTUUTTTTTUUVVVVVVUTSSSSSSSTTTTTSSSSSSSSSSSSSSRRRRRRQQQQQQQPPPPPPOOOOOOOOOOOOPOONNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLKKKKKKKKKKJJJJJIIHHHIIIIIIIIIIIHHIIIIIIIIHHHHHHHHHGGGHHHGGGFFFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEFFFFFEEDDCCDDDDDDDDDCCBCBBBBBBBBBBABBCBBAAAAAAAAAAAAAAAAAAAAA@?@@@?>==>>??>=========>============<<<=============<<<<=<<<<<<;;<<;;;;<<<<;;IIIIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJJKKLLLLLLLLLLLLLLLLLLLLLLLMMMNNMMMMMMMMMMNNNNNOOONNNNNNNNNNONNOOOOOOOPPPPPPPPQQQQQQQQQQQQQQQQRRRSSSSSSSSRRRRRRRSSRRSSSSSTTTTTTTSTTTTTUUUUUUUUUUVVVVVVUUUUVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWXXXXXXWWWWXXXXXXYYYYYYYYYYYYYYYYXXXYYYYYYZZZZZZZZZZ[[[[[[[Z[[[[[[[[[[ZZZZYYYZZ[\]^^\\\\\\]]]]]]]]]]\\\\\]]^^^^^^^^^^_________^^^__`_^^_aa``````____^^__^]^__^^`bc`\]`cda^^`dgfR9% !!!!#"#(+-+&&.-%#$ %$ !%.>P^hjebadfefgihdefhgfeegggffffeeeeeeffgfffffffffffffffffffffffffeeeeeeeeeeeeeeefffffffffffeeeeeeeeeeeeeeddddddddddddcccccccccccccbbbbbaaaa`aaabbbbbaaaaabbbbabbbbbbbaaa`aaaaaaa```aaaaaaaaa`````````````________`____`____^^_^^^^^^^^]]]^^^]]^^^^^]]\]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZZZZZYYYYZZZZZYYYYYYYYZZYYXXXXXYYYXXXXXXXXXXXWWVVVWWWWWWWVVVUVVVVVVVUUUUUUUUUUUUUUUUUTTTUUUUUVUVUUUUTTTTTTTTTSSSSTTTTSSSSTTTRRRRRQQQPPPPPPPPPPPPPOOOOOOOOOOOONONNNNNNNNNNMMNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLKKKKKKJIIIIIIJJJJJJJIIIIIIHHIIIIIIIIIHHHHHHHGFFGGGGGGGGGGFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDCDDDDDDDDDCCCBBBBBCBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>>>>>>>>>==============<====<<<<<<<<<<<;;;;;;;;;<<<<;;JJIIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJKKKLLLLLLLLLMMLLLLLLLLLLLLMMMNNNNNNNNNNNNNNNOONNNNNNOOOOOOONOOOOOOOPPPPPPPPQQQQQQQQQQQQRQQQQQRRRSSSSSSSSRRRRRRRSSSRSSSSSTTTTTUUTTTTTTTTTTTTTTTUVVVVVVUUVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWXXXXXXXWWXXXXXXXYYYYYYYYYYYYYYYYXXXYYZZZZZZYZ[[[[[[[\\\\\[[[[[\\\\[[[[ZZZZZZ[[[\]]]]\\\\\]]]]]]]]]]]]]]]]]^^^^^^^^^___________^_____^^`a````````____`a`^^_``_^`ba`^`aba`abbb_VB, ## %(**)&" #(+( !" !# &6IZeggeffefggfeeeeeddfggfeefffffffffggggffffffffffffffffffffffffeeeeeeeeeeeeeffffffffffffeeeeeeeeeeeeeeedddddddddddddddccccccccccbbbbbaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbaaaaaaabaaa`a``````aa```_`````````_______````````___^___^^^^^^^^]]^^^^]^^^^^]]\]]]]]]]\\\\\\\\\\\\\[[\\\[[ZZZZZZZZZZYZ[[[[ZZZYYYYYYZZYYXXXXXYYYYXXXXXXXXXXXWVVVWWWWWWWWVVVVVVVVVVVVUUUUUUUUUVVVUUUUTTTTTUUUUUUVVUUUUUTTTTTSSSSTTTTSSSSTTTRRRRRRQQQPPPPQQPPPPPPPPPPPOOOOOOOONOOOOOOOOONNNNMMMMMMNNNMMMMMMMNMMMMMMMMMMLLLLLLLLKKKKKKJJJJJJJJJJJJJJIIIIIIHHHHHHHHHHHHHGHHHHGFGGGGGGGGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDCCCCBBBCBBABBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>=>>>>>>>>===>=========<<<<<<<;<<<<<<<<;;;;;;;;<<<<<<JJJIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJKKKKLLLLLLLMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNONNNNOOONNNNNNNOOOOOOOPPPPPPPPPQQQQQQQQQQQRRQQQQQRRRSSSSSSSSRRRRRRRSSSSSSSSSTTTTTUUUUUUUUTTUUUUTTUUUVVVVVUUVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYXXYYYYZZZZZZZ[[[Z[[[\[[[[\\[[[\\\\[[[\[[[[[\\\\\\\\\\\\\\]]]]]]]]]]]]]^^]^^^^^^^^^^___________________`a`_`````____aba`__`bb`_`a_`bcb_^`cc^WK>/# $$!%,,*&" #&&$ !"%#!!# .@R]ceeeefgeeggfeddfgggfefgggggggggggggfgggggggggggggggffffffffffffffffffffffgffffffffffffffffffffffffeeeedddddddddddddcccccccccbbbcbbbbbbbbbbbbbcbbbbbbbbbbbbbbbbbbaaaaaaaaabaaaaaaaaaaaa``````````````_____`````aa````________^^^^^]^^^^]^^^^^]\\]]]]]]]\\\\\\\\\\\\\[[[[[[\[[[[[[[[[[ZZ[[[[ZZZYYYYYYYZYYYYYYYYYYYYXXXXXXXXXXXWVWWWWWWWWWWVWWVVVVVVVVVVVVVVUUUUUUVVVUUUUUUUUUUUUUUUUUUUTTTTTTTSSTTTTTTTTTTSRRRRRRQQQQPPQQQPOOOPPQQQPPOOOOOOOOONNNOOOOONOOONNNNNNNNNNNNMMMMMNMMMMMMMMMMLLLLLLLLKKKKKKKJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHGGGGGHGGGGGFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDCDCCBCCCCBBBBBBBBBBBBAAAAAAAAA@@@@@@@@@??@@??????????>??>>>>>>>>>=>>>>>>>>>>=====>===========<<<<<<<<<<<<;;;;;;;;;;;;;JJJJIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOONOOOOPOOPPPPPPPPPPQQQQQQQQQQRRRQQQRRRRRSSSSSSSSRRRRRRSSSSSSSSSSTTTTUUUVVVUUUUUUUUUUUUVUUVVVVUUVVVVVVVVVVWWWWVVVVVVWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYYZZZ[[[[[[Z[[[[[[[[[[[[[\\\\[[[[[[[\\]]]]\\[[\\\\\]]]]]]]]]]]]]^^^^^^^^^^^^^___________`_______``````````````aa`^^_abba__``bbb`^^_a\PC5) $'$!(.,'"! !%'#!!## $%! "&"$4CPX_bdghgghhgggghhggggggggggggggggggfggggggggggggggggggggfffffffffffffffffgggffffffffffffffffffffffeeeeedddddddddddddcccccccccccccccbbbbbbbbccccccccbbbbbbbbbbbbbabbbbbbbbbbaaaaaaaaaaaaa`````````````____````aaaaaaa``````___^^^^^^^^^]^^^^^]]\]]]]]]]]]\\\\\\\\\\\\\[[[\\\\\[[[[[[[[[[[[[ZZZZZYYYYYZZZZYYYYYXXYYYXXXXXXXXXXWWWWWXXXWWWWWWWWVVVVVVVVVVVVVUUUUUUVVVVUVVVVVUUUUTUUVVUUUTTTTTTTTTTTTUUUUTTSSSSSRRRRRQQQQRRQPPOPPQQQQPPOOPPOOOOOONOOOOOOONOOOOOOOONNNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKJJJJJJJJIIIIIIIIIIIIIHIIIIIIIIIIHHHHHHHHHHHHHGGFFFFGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDEEEEEDDDDCCCCCCCCBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>=>??>>>>>>>>>=>>>>==========<=<<<<<<<<<<<<<<<<;;;;;;;JJJJJIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKKKLMMMMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOONNOOOOOPPPPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRSSSSSSSSSSRRRRSSSSSSSSSSTTTTTTTVVUUUUUUUTTTUUUUUUUVVVVVVVVVVVVVVVWWWWWVUVVVVWWWWWXWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZYYYYZZZ[[[ZZ[[[[[[[[[[[[[\\\\\\\\[[[[\]]]]]]]\[[\\\]]]]]]]]]]]]]^^^^_^^^^^^^^^___________```__^^^_``````````````__^^^_`aa_^^_bb``a`^XRF9-$#'(#%('&$ !! "&%! #$" %"#' "+6ALW`ghfdffgghhihggghhfggghhhhhhgggggggggggggggggggggggggfffffffffffffffffggffffffffffffffffffffffeeeeeeeedddddddddddddccccccccdddddccbbbcccddddddcccbbccccccbbbbabbbbbbaaaaaaaaaaaaaaaa``````````````_`___```aaaaaa```a```____^^^^^^^^^^_^^]]\]]]]]]]]]]\\\\\\\\\\\\\[[\\\\\\[[[[[[\[ZZZZZZZZZZZZYYZZZZZZYYYXXYYYYXXXXXXXXXXXXXXXXXWWWWWXWWWWVVVVVVWWWWVVVUUVVVWWWVUUUUUUVUUTUUVUUTTTTTTTTTTSSSTTUUUTTRRSSSSSSRRRRRRRQPPPPPQRRQQPPPPPPOOOOOOOOOOOOOOOOOONONNOONNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKJJJJIIIIIIIIIIIIIIHHHHHHIIIIIHHHHHHHHHHHHGGFFFFGGGGGFFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEDDEEEEEEEEDDDDCBBBBBBBBBBAAAAAAABAAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>=>???>>>>>>>>>>>>>>>>=======<=<<<;<<<<<<<<<<<<;;;;;;;JJJJJJIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKKLLLMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNONNNNNNNNNNNOOOOOOPPPPPQQQQQQQQQQQQQQQQRRRRRRRRRRRSSSSSSSSSSSSRRRSSSSSSSSSSTTTTTTTVVVVUUUUUUUUTUVUUUUVVVVVVVVVVVVVVWWWWWVUUVVVVWWWWXXXXWXXXXXXXXXXYYYYYYYYYYYYYYZZZZZZZZZZZZZZZYYYYZZ[[[[[[[[\[[[[\\\\[[\\\\\\\\[[\\\]]]]]]]]\\]]]]]]]]]]]^]]]]^^^__^^^^^^^^___________`````__^__``````___``````____```_____`]ZZ[[TG:-% $((% !(&" $'!!" !$"&"%*5EXejgddefffghhgggihgggghhhhhgggghhhhhhhhhhhhhhhhggggggggggggggggggggggggggfffffffffffffffffffffeeeeeeeeedddddddddddddddddddcddddddcccccccddddddcccccccccccccbbbbbbbbbbbaaaaaaaabbaaaaaaaaaaaa````````_^___`````````a`````___^^^__^^^^^__^^]\]^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\[[[[[[[[ZZZZ[ZZZZZZZYYZZZZZZZYYXXYYYYYXXXXXXXXXXXXXXXXWWWWWXXWWWVVVVVVWWWWVVVUUVVVWWWVVVUUUUUUUUTUVUUUUTUTTTTTTSSSTTUUUTTRRSSSSSSSRRRRRRQPPQQQRSRQQPPPPPPPPPOOOOOPPPPOOOOPOOOOONNNOONNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKJJJJIIIIJJJIIIIIJIIIIIIIIHHHHHHHHHHHHHHHHHGFFFFGGGGGGFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEEDEEEEEEEEDDDDCCCCCBCCCCBBBBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>=>???>>>>>>>>>>>>>>>>>>>>>>=<===<<;;;;;;;;;;<<;;;;;;;JJJJJJJIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKLLKKKKKLLLMMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOOPOOOOPPPPPQQQQQQQQQQQQQQQRRRRRRRRRRRSSSSSSSSTSSSSSSSSSSTSSSSTTTTTUTTTVVVVUUUUUUUUUUVVUUUVVVVVVVVVVVVVWWWWWWWVVVVVVWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZ[[[[[[[[[[[[\\\\\[[\\\\\\\\[[\\\\\]]]]]^^^]]]]]]]]]]^^^]]]^^_____^^^__________``___````_____``````____```aaaaaaa```abb`ZTPONKA2% !! !! %*+(#"'&! ! ''!$#$$ #3I\ghgeffdcdfhhgghhfghgggggggffggggggggggggggggggggfffffffffffffffffffffffgffffffffffffffffffffffffeeeeeeeeeeeedddddddddddddddeddddccccccdddddddcccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa`aa`````_^^___``````_```````________^^^^^___^]\^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[ZZZZZ[[[[[[[ZZZZZYYYZZZZZZZYXXYYYYYYXXXXXXXXXXXXWWWWWWWWXWWWWVVVVVVWWWWVVVVVVVVWWWVVVUUUUUUUUUUUUVUUTTUTTTTTSSSTTUUUTTRRSSSSSSSSRRRRRQPPQRRSSRQPOPPPPPPPPPPPOPPPPPOOOOOOPPOONONNNNNNNNNNNMMMMMMMMMMLLMMMLLLLLLLLLKKKKKJJJJIIIIJJJJIIIIIIIIIIIIIHHHHHIIIHHHHHHHHGGFFFFGGGGGGFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEEDEEEEEEEEDDDDCCCCCBBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@?????????>>??>>>>>>>>>>>>>>>>>>>>==========>>>>>>=<<<<=<<<<<<<<<<<<<;;;;;;;JJJJJJJJIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKLLLLLLLLLMMMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOOOOOOOPQQQQQQQQQRRRQQQQQRRRRRRRRSSSSSSSSSSSSSTSSSSSSSSSSTSSSSTTTTTUUUUUVVUUUUUUUUUUVVUUUUVVVVVWWVVVVWWWWWWWWWVVVWWWWWXXXXXXXXXXXXXYYYYYZZZZZYYYYYYYZ[[[[[[[[[[[ZZZZZZZZ[[[[[[[[[[[[[[\\\\\[\\\\\\\\\\[\\\\\\\\]]^__^]]]]]]]^^^^^^^^^^________________````___`````___`````______````aaaba``aceee_SIC>94+# #"! !! )-,'"!%((" !$" $)& "&&"%6IZfkijifdcfijhfggfggggggggggggggggggggggggggggghgggggggggggggggggggggggggggffffffffffffffffffffffffeeeeeeeeeeeeeddddddddddddddddddccdddddddddddddcccdddcccbbbccccbbbbbbbbbbbbbaaaaaaaaaaaaa`aaaa``a`_____````____`````_________^^^^^___^]]^^^^^]]]]]]]]]]]]]\\\\\\\\[[[[[ZZ[[[[[[[[[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYXXXXXYXXXXWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUVUUUUUTTTTTTTTTUUUUTTSSSSSSSSRRRRRRQPQQRRRRRQQPOPQQQPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMLLMMMMLLLLLLLLKKKKKJJJJJJJIJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGFFFGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDEEEEEDDDDCCCCCCBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>==>>>>>=================>>>=<<<<<==<<<<<<<<<<<;;;;;;;JJJJJJJJJJIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKLLLLLMMMMMMMMMMMMMMMMMMLLMMMMNNNNNNNNNNNNNNOOOOOOONOOOOOOOOOOPQQQQQQQQRRRRQQQQRRRRRRRRRSSSSSSSSSSSSSTTSSSSSSSSSTSSSSTTTTTUUTTUUUUVVUUUUUUUVVVVUUVVVVWWWVVVWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYZZZZZZYYYYYYZ[[[[[[[[[[[ZZZZZZZ[[[[[[[[[[[[[\\\\\\\[\\\\\\\\\\[\\\\\\\]]]^__^]]]]]]^^^^^^^_^^^_______________`````__``````__``````______``aaa``aa``adeb]TG=6/(" #%#! %-/,&"!#%,.'%&&#!" "$#&'""$#%0@R_eikifehjjhfgggghggggggfffghgggggggggggggghggggggggggggggggggggggfgggggggffffffffffffffffffffffffeeeeeeeeeeeeeeedddddeedddddddddddddddccddddddcddddddccccbcccccbbbbbbbbbbbbbaaaabbbbbbbaa`aaaaaa`_____````___________________^^^____^^]^^^^^^]]]]]]]]]]]]]\\\\\\\[[[[[[[Z[[[[[[[[[[[[[[[[[ZZZZZZZZZZZYYYYYYYYYYYXXXXYYXXWVVVVVVVWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUVVVVVUUUUUUUUTUTTSSSTTTTTTTTTSSRRRRRRRRRRRRQQQRSRRQPPPPPQQQQQPPPPPPPPPOOOOOOOOOOONNNNNMNNNMMMMMNMMMMMMMMMMLLMMMMMLLLLLLLKKKKKJJJJJJJJJJJJJJJIIIIIIIIIJIIIHHHHIHHHHHHHHHHHGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDEDDDDDDDDDEEEEDDDCCCCCCCCCBBBBCBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>=>>>>==============>====>>=<<<<<=<<<<<<<<<<<<;;;;;::KJJJJJJJJJJIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMLLMMMNNNNNNNNMNNNNNNONNNNNOOOOOOOOOOOPQQQQQQQQRRRRRQQQRRRRRRRRRRSSSSSSSSSSSSSTTTSSSSSSSTTSSTTTTTTUUUUUUTTUUUUUUUUUUVWVVUUUUVVWWWVVVWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYZZZZZZZZZYYYZ[[[[[[[[[[[ZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]^^]]]]]]^^^^^^^___^^^^^___``_______`````````_```````__``______``ab```aa```c`YNB70-'!!!"&'$ "*/0+%"!"$&(.1,+,*$$&#!%%" !%!!! (9KXbhihfhiihghhhhhggggggggggggggggggggggggggggfggggffggggggggggggggggggggggffffffffffffffffffffffffffeeeeeeeeeeeeedddeeeddddddddddddddccccdddddcdddddddcccccccccccbbbbbbbbbbbbbaaaaaaaaaaaa`````a``````````____^^^______```___^^_____^]]^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZYYYYYZZZYYYYYYYYYYXXXYXXWVVVVVVVVVVVVVVVVVVVUUUUVVVVVVVVVUUUUUUUUUVVVVVVVUUUUVUUUUTTTSSTTTTSSSSTTTRRRRRRRRRRRRRQRSSSRPOOOPQQQQQQQPPPPPPPPOOOOOOOONNONNNNNNNMMMMMMMNMMMMMMMMMMLLMMMMMMLLLLLLKKKKKJJKKKKKKJJJJJJJIIIIIIIIIIJJIHHHHIHGGHHHHHHHHHGGFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDCCCCCCCCCCBBBBCBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>===========<<<<<<<===========<=======<<<<<<<<;;;;;::KKJJJJJJJJJJIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKLMMMMMMMMMNNNNNNNNNNNMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOPPPPOPPQQQQQQQRRRRRRQQRRRRRRRRRRSSSSSSSSSSSSSSTTSSSSSSSSTTSSSTTTTTUUUUUUUUUUUUUUUUUUVVVVUVVVVWWWWVWWWWWWWWWWXXXXXXXXXXXXXXXXXXXXXYYYYYYZ[[ZZZZZZZYYYZ[ZZZZZZZZZ[ZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]^^^^^^^___^^^^^__``________``````a`___``a``___``______``aaaaaba````YL=0(&'&# $((% !).0/*%""%'*,/0/..*#!&($"$" !!"" "!&5ET`gifgggggjihhhggggfggggggggggggggggggggfgggggggfffggggggggggfgggfgggggggfffgggggggggggggfffffffffffffeeeeeeeeeeeeeeedddccddddddddccccccddddddddddddcccccccccccbbbbbbbbbbbbbaaaaaaaaaaabaaaaaaaaaaaaaa````_^^^^_____````___^^_____^^]^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZ[[[ZZYYYYYZZZZZYYYYYYYYYXXXXXXWVVVWVVVVVVVVVVVVUUUUUUUVVVVVVVVUUUUUUUUVVVVVVVVVUUUUUTTUUTSSSTTTTSSSSTTTRRRRRRRRRRRRRRSSRRPNMMNPQQQQQQQQPPPPPPPOPOOOOOOOOONNNNNNNMMMMMMMNMMMMMMMMMMLLMMMMMMLLLLLLKJJJJJKKKKKKKJJJJJJJIIIIIIIIIIIIIHIIIIHGGHHHHHHHHHHGFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDCDDDDDDDDCCCCCCCCCCCCBBBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>========<<<<<<<<<<<<========<<<<<<<<<<=<==<<<;;;;:::JKKKKLLLKKKJJJJJJJJIIIIIIJJJJJKKKKLLLLLLLLLMMMMNNNMMMMMMMMNNNNMNOONNNNNNNNNNOOONNNNNOOOOPPOOOOPPPPQQQQQRQQQQRSRRQQPQRRRRRRRRRRRRSSSSSSSTTTTTSSSTTTTTTTTTTSTTTTSTTUTTUUUTTTTTUUUUUUUVVVUUVVVWWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXYYYYYYYYYYYZZZZZZZZZZZZZZ[[[[[[[ZZZZZZZZ[[[[[[[[\\\\\\\\\\\\\\[\\\\\\\\\\\]^]]]\\\]]]^^]]^__^^^^^^^________________``````````a````````````aaa````````aa`ab`_`bc]O=/&% ! !"#$%&$ $-320.)$#).00//,&'+'# $%## "!!!#,?Uhkdfhhffiiggggggfffgggggfggggggghhhggggggggggggfffffffffffffgggfffffffggffggggggggggggggffffffffffffffffffffffeeeeeeeeeeeeeeedddddddddddddddddddcccccccbbbbbbbbbbbbbaaaabaaaaaaaaaaaaaaaaaaaaaa`a````````____```````____``````___^^^__^^^^]]]\\]]]\\\\]]]]]\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZYYZZZYYY[ZXWYZZYXXXWWXXX[ZWVWZYVWXYWUVVWWWUUVWVUVWWXXVTSWWUTUUUTTUVUUUTUWWVUUVTSTUWSQTTSTUTSSUUTSRQSTSQPQRSSRQSUSPMKHGGHJKLOQPPPPPQQQQQQPPPPOOOOOOPPPONNNNNNNNMMMMNMMMNNONLKKLLMMMMMMLLLLLLKKKKKKKKKKKKJJIIIIIJJJJJJJJJIIIIIIIIIIHHHHHHHHHGGGGFFFFFFFFFFFFEEFFFFEEEEEEEEEEEEEEDDDDDDDCDDDDDDDDDDCCCCCCCBBBBBBBBBBBBBBBBBBAA@??????@@@@@@@@??????????>>>>>>>>==>==============<<<<<<<<<<<<<<==<<<<<<<<<<<<<<<<<;;;;;;;KKKKKLLLKKKKKJJJJJJJJJJIIJJJJJKKKKLLLLLLLLLLMMMNNNNMMMMMMMNNNNNNOOOOONNNNNOOOOOOONONOOPOPPOOPPPPPQQQQQRRQQQRRSSRRQQRRRRRRRRRRRRRSSSSSSSTTTTTTSSTTTTTTTTTTTTSTTTTTUTTTTTTTUUUUUUUUUUUUUVVVVVWWWWWXXWWWWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZ[[[[ZZZZ[[[[[[[[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\]^^]]]]\\]]]^^]]]^^^^]]^^^________________`````````aaaaaaaaaaaaaaaa``___````aa``ba`bdcYG5(!" #')))('# .431/.,)&$&*-./.,(#!*23+""&'&#! 0Jcjfgijihhhgggggggggggggggggggggghhhhhhhghhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccbbbbbbbbbbbbbbbaaaaaaabaaaaa`a``_`````````````````___``````____^___^^^^]]]]\\]]\\\\]]]]]]\\\\\\[[[ZZZZZ[[[[[[[[[[[[ZYYZZZZYYYYYY[ZVVWYYXXYYYYZYXYXTTVZZVUUVVWXVTTUVVVVUVWVTUWWVUXXUUUWXWVUUTTUVUUUTTUVUTTVXUSUTRSTTSSTUTTRQRTTSRQPQPPOONNMLLLLLMMNNOQQQQQQQQQQQQPPPPOOOOOPPQPNNNNNNNNNMMMNONMNNOONLKKLMMMMMMMLLLLLLLLKKKKKKKKKKKJIIJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEFEEEEEEEEEEDDDDDDDDEEEDDDDDCCCCCCCCCCCCBBBABBBBBBBBA@??????@@@@@@@@@??????????>>>>>>>>>>==>>===========================<=======<;;<<<<<;;;;;;;KKKKKKKKKKKKKJJJJJJJJJJIIJJJJJKKKLLLLLLLLLLMMMMNNNNMMMMMMMNNNNMNNNNNNNNNNNOONNNOOOOOOOOOOOPOPPPPPPQQQQRRQQQRRSSSRRQRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUUUUVVVVVVUUVVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZ[[[[[[[[[[[ZZ[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]^^]]]]\\]]]^^]]]^^^^]]]^_________________``````````aaaaaaaaaaaaa```__^^__`abb``a`acd_R?.$ #'+./-*(# )340/.//.+))*++//($#%+275,$ %(&# !! (@\jkiiijjihhhhhhhhhhhhhhggggggghhhhhhhhhgggggggggggggggggggghgffffffffffffffffffffffffffffffgffffffffffffffffffffeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccbbbbbbbbbbbbbbbbaabbbbbbbbbaaa````aaaaaaa``````````__``````___^^___^^^^]]]]]]]]\\\\]]]]]]\\\\\\[[[[ZZZ[[[[[[[[[[[[[ZYZZZZYYXXYZZ\ZXWWYYXYZ[ZZ[ZXXVUUVXXWVUVWWUSQRUXXWTTVWVSTVXXWXWUTUWXXWUTTVWXVUTTUUUUTTUVVVUTRSTUTSSSUUTRRSSTTRQQPOOKHJLNQRSTSRQPQQQQQQQQQPPPPPPPPOOOOOPPPPNMNNNNNNNNNNNNMMMNOONMLLMMMMMMMMLLLLLLLLKKKKKKKKKKKJIIJJJJJJJJJJJJIIIIIIIIIIHIIIIHHHGGGGGGGFFFFFFFFFFFGGFFFEEEEEEEFEEEEEEEEEEEEDDDDDEEEEDDDDDCCCCDDDDCCBBCBBBBAAAAAABBA@???@@@@@AAA@@@@@????????>>>>>>>>>>==>>>==========================<=======<<<<<<<<;;;;;;;LLKJJJJJJKKLLKKKKKJJJJJJJJJJJKKKKKLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNNNNNNNNOOOONOOOOOOOOOOPPPPPPPPPPPPPQRQQQRRSTTSRRRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTTUUUUUUUUUTUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[[[[[[[[[\[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]^^]]]]]]]]]^^^^______^^__`_______________``````````aaaaaaaaaaaaa`````__`abbbbba`_`cbZJ7'"'+.01,&#%,121/../.,*((+-.32'"#(-230*%#%$!'% $9Wjojfdfijihhhhhhhhhhhhhggggggghhhhhhhhhhhgggggggggggggggggghggggggggggggggggggggggggggggggggggffffffggffffffffffeeeeeeeeeeeeedddddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaabaaaa`aaaaaaaaaa``````````````````_____^^^^^^]]]]]]]]]\\]]]]]]\\\\\\[[[[[[[[[[[[[[[[[[[[[[[ZZYYZZZZ[\^^\[ZZYYYZ[YXYYXXY[ZXVVXZ]]YSMMQUY[YVTTVWWWXYYXVVURRRQRTTSSUWXWVWWWVUSSTSSSTVVUUUVWWUTTUTTUSQQRTTUUTQNLMNOQSTTSSRQPQQQQQQQQQPPPPPPPPPPOOOPPQPNMNNNNNNNNOONNMMMNOONMMMNMMMMMMMLLLLLLLLLLKKKKKKKKKJJJJJJJJKKKKKJJIIIIIIIIIIHIIIIHHHHHGGGGGGGFFFFFFFFFGGFFFEEEEEEEFFFFFFEEEEEEEEDDDDDEEEDDDDDDCDDDDDDCCCCCCCBBBBBBBBBBAAA@@@@@@AAAB@@@@@@@@@@????????>>>>>>>>>>>>========================<=======<<<<<<<<<<;;;;;KKKJJJJJJKKKKKKKKKKKKKKJJJJKKKKKLLLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNNNMMNNNNONNNOOOOPPOOOOPQQQPPPPPPPPPQRQQQRRSSTTSSRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTUUUUTTUUUUUUUUUUVVVVVVVVWWWWWVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZZZZYZZ[[[[[ZZZZZZZZ[[[[[[[[\\\\\\\\]]]]]\\\\\\\\\\\\\\\\\\\\\]]]]^^]]]]]]]]]]^^__````___`aa_______________``````````aaaaaaaaaaaaa``aaa```aaaa````abb\P?.$! &*,,+*% %152+,110.*&"#&-3672& !',..+(&%! !#&#.I_kkhefhkkiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffffffffffffffffffgfffefffgffffffffffeeeeeeeeeeefeeeeeeeeeeeeeeeeddddddccccccccccccccccccbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaabbbba```a``````````````````_____^^^^^^^]]]]]]]\\]]]^^]\\\\\\\\\[[[[[[ZZZZZZZZZZZZ[\[ZZZ\\]\[YXYZZZZYXXXYYXXYZ[ZZ\[XUWZ[[YVQKMRVXXWWYZZWUUWXVUVWTPOMHGJLNOQSSQOSUUVUTUWVUSUVTTTSTUUUTUUSTWWVWVUPNMMMKMOPPQQRRRQRRRRRRRRQQQQPPPPPPPPPPPPPPPQPNNNNNNNNNNNNNNMNNNNNNNMNNMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIIIIIIHHHHHGGGGGGGGFFFFFFFFFGGFFFEEEEFFFFFFFFFFFFEEEEDDDEEEEEEEDDDCCCDDEEEDDCCBCCBBBBBBBBBBAABAA@@@@AABBA@@A@@@@@@@@@??????????>>>>>>>>=======================<<<<<<<<<<<<<<<<<<<;;;;KKKKKKKKKKKKKKLKKKKKKKKKKKKKKKKKLLLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNMMMMNNNONNONNOPPPPPPOOPQQQQPQQQQPPPQRQQQRRSSTTTSSRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTUUTTUUUUUUUUUUVVVVVVVVWWWWWWWWVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZZZZZZZ[[[[Z[[[[[[[[[[[[[[\\\\\\\]]\]]]]]]\\\\\\\\\\\\\\\\\\\]]]]]]]^]^^^^^^^^]^__`````````aa`______________``````````a``````````aa``aaaa``aaa__bccdb]P@0$ !!#+-*'$""+33/).651,$"*1685-#"(-..+'$" %'% #5IZejkjhhhiiiiiiiiiiiiihhhhhhhhhhhhhiihhhhhhhhhhhghhhhhhhhhhggggggggggggggggggggggggggggggffffgfffffffgfffffffffffffffffffffeeeeeeeeeeeeeeeeedddddcccccccccccccccccccccccccccccbbbbbbbbaaaaaaaaaabbbbbbbcbaaa``aa```````````````____^^^^^^^^^^^^^]]\\]]]^^]]\\\\\\\\\[[[[[[[[[[[[[[[[[\[ZYYZ[\\\ZXWVWYZ[[ZYXXXXXXYZWUVWYZ\]ZVQQPLNTVXYXVVWWTRQPNKKNQSROH>;=@CEGHHGGMPQRSSVZZXSUVTTTRQRSSRQPMIGJNRUTQNLMLKLOPPQQRRQQRRSRRRRRQQQQPPPPQQQQQPPPPPPPPOOOOOOONNNMMMNNNNNNNNNNNNMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIHHHHHHIHHHHHHGGGGGGFFFFFFFFGGFFFFEEFFFFFFFFFFFFFFEEEDDDEEEEEEEDDDCCCDEEEEEDCCBBBBCBBBBBBBBBBBAA@@@AABBCAAAAA@@@@@@@@@@@?????????>>>>>>>===================>==<<<<<<<=<<<<<<<<<<<<;;;JJJKKKKKKKJJKLLLKKKKKKKKKKKKKKKKLLLLLMLLLLLMMMMNNNNMMMMMMMMNMMMMNNNMMMMNNNNOONOOOPPPPPPOOPQQQQQQQQQQPPQRQQQRRSSTTSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTUUUUUUUUVVVUUUUVVVVVVVVWWWWWWWWWVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZ[[ZZZZZ[[[[[[[[[[[[[[\\\\\\\\]]]]]]]]]\\\\\\\\\\\\\\\\\\\]]]]]]]]^^^^^^^^^^]^__`_______``````````````___``````````aaaaaaaaaaaaa`aaa````aba_^bc`^XN?0$ '..("!#&+..++.5:7/& (0342+# %+/0/,&" &*(" !#"0BVfonifgiiiiiiiiiiiiihhhhhhhhhhhhiiiihhhhhhhhhhghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffffeeeeeeeeeeeeeeeeeddddccccccccccccccccccccccccccccccbbbbbbbaaaaaaaaaabbbbbbbbbabaaaaaaaaaaaaa``````````^^^^^^^^^^^^^^^]\]]]]^^]]]\\\\\\\\[[[[\[[[[[[[[[[\\[ZZZZYYXXWVVUSTVXZ[ZYXXXYXVVWTRQTY]_\WQMPRLOUXZ\WQMKLLKID@>?BFMRL@500367899;@EHHJLMPUXWRTVUUUPMNNNLJHD>:<ADGJLMNOONNNOQRSSSRRRRRRRRRRQQQQPPPPQQQQQQPPPPOOOPQPPOOONNNNMMNNNNNMMMNNNNMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJIIIIIIIIIIHHHHHHHIHHHHHGGGGGGGFFFFFFFGGFFFFFFFFFFGGGFFFFFFFFFFDDDEEEEEEEDDDCCCDEEEEDCCCBBBBBCBBBBBBBBCBAAAAAAABBBAAAAAA@@@@@@@@@@??????????>>>>>>>=====================<<<<<<<<<<<==<<<<<<<<<<IJJKKKKKKKJIJLLKKKKKKKKKJKKKKKKLLLLLMMLLLLLMMMMNNNNNNNNNNMMMMMMMMNNNNNMNNOOOONOOOOOPPPPPPQQQQQQQQQQQQQQRRRQQQRSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTUUUUUUUUUUVVVVVVVWWWWWWWWWWWWWWWXXXXYYXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[\\\\\\\[[[\\\\\\\\\\\\]]]]]]]]\\\\\\\\\\\\\\\\\]]]]]]]]]]^^^^^^^^^^]]^^^^^^^^^^^_````````````___``````````aaaaaaaaaaaaaaaaa``abddca^]XPG>6,$#*/,%!(.0/*'+7;93* '041+#&-0/,)&"!&)("# -AWglkkkjjjjjjjjjjjjiiiiiiihhhhhhiiiiiiihhhhhhhgggggggggggggggggggggggggggggggggggggggggggfgggggggggggggfffffffgfffffffffgffffffffffffffffeeeeddccccccccccccccccccccccccccccccbbbbbbbbaaaaaaaabbbbbbbbbbaaaaaaa`aaaaaaa``````````^^^^^^^^^^^^^^^]]]]^^^^]]]\\\\\\\\\\\\\[[\\\\\\\\\\ZZ]^_^[VQMKJJJLNPRTWXXWWXYUSRTVWXWURPPRUWUKNX[\YQGA>>AA=857899>EA7-'')*+,,-4==<;<=<>DKOORVXXULGGGFCCDBEMH?5//5=DJPWUQRTTUTSRQQQQQRRRRQQQQQQQPQQQQQQQPPPOOPPPPPPOONNNMMMMNNNNMMMMNNNMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIIIIIIIIIIIIIIHGGGGGGGFFFFFFGGFFFFFFFFFFGGFFFFFFFFFFEDEEEEEEEEEEEDCCDDDDDDDCBCBCCCCCBBBBBBBBBBBAAAAAABBBAAAAAA@@@@@@@@@?????????>>>>>>>>>>====================<<<<<<<=<<<=<=<<<<<<<<<JJJJKKKKKJJJJKKKKKKKKKJKJJKKKKLLLLMMMMLLLLLMMMMNNNNNNNNNNMMMMMMMMNNNNNMNNOOOOOOOOOOPPPPPPPPPPPQQQQQQRRRRRRQQQRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTTUUUUVUUUUUVVVVVVWWWWWWWWWWWWWWXXXXXYYXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZ[[[[[[[[[[\\\\\\\\\\\\\\]]]]]]]]]\\\\\\\\\\\\\\\\]]]]]]]]]]]^^^^__^^^^]]]^^^^^^^^^^_````````````___``````````aaaaaaaaaaaaa``aaaaabb`]XRG<3*$"! !!"',-(!!#)/0-((-8:5.&$-33-$ &.31,&" #$# "/@R]fpojjjjjjjjjjjjiiiiiiiiiihhhiiiiiiiiiihhhhggggggggggggggggggggggggggggggggggggggggggggggggggffgggggggffffggggggggggggffffffffffffffffeeeedcccccccccccccccccccccccccccccccbbbbbbbbbbaaaabbbbbbbbbbbbaababaaaaa``aaa`__``````_^^^^^^^^^^^^^^]]]]]^^^^^]]\\\\\\\\\\\\\[[[[[[[[[[[[[[[YVSOLKMPTVUTSQQTWZXVTUWXVTZ]ZSJB;:BNX\XLNURLF?;<@B?<:9<BFE>:95.&"! !$%&'0:;9675.*,4:<@DCDECEKMJB?@>EOJ>0'$(-08DTVRSTSTSRQQRSTSRRRRQQQQQQQQQQQQQQQQPPOOOPPPPPOOOONNNNNOOONMMMNNNMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKJKKKJJIIIIIIIIIIHHHHHHHHIIIIIHGGGGGGGGGFFFFGGGGFFFFFFFFFFFFFFFFFEEEEDDEEEEEEEEEDDCCCCDDDDCCBBBBBBBBBBBBBABBCBBBBAAAAAAAAAAAA@@@@@@@@??????????>>>>>>>>>>>>>>=================<<<<<<==<<<=<<<<<<<<<<<KJJJJJJJJJJJKKKKKKKKKJJJJJKKKKLLLLMMMMLLLLLMMMMNNNNNNNNNNMMMMLLMMNNNNNNNNOPPPPPOOOOOPPPPPPPPPPQQQQQRRRSSRQQQQRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTUTTTTTUUUTTTTUUUUUUUUVVVVVWWWWWWWWWWWWWXXXXXYYYXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZ[[[[Z[[[[[[[\\\\\\\\\\\]]\\\\\]]]]]]]]\\\\\\\\\\\\\\\]]]]]]]]]]]]^^^___^^^^]]^__``_______````````````___```````_``aa```aa``aaaa``abaa`_]WOF<." ! #&+-)" ',-*'&)-33/+&#'-33.(#"%+/34/)%# ##"&-9FWinjjjjjjjjjjjjjiiiiiiiiihhhijiiiiiiiiiihhhhhhhhhhhhhhgggggggggggggggggggggggggggggghggffffggggfffffgfffffgffffgfgffgfffffffffffffffffeeeddcccccccccccccccccccccccccccccccbbbbbbbbbaaabbbbbbbbbbaaabbaaaabbaaaaaaa``````````^^^^^^^^^^^__^]]]]]^^^^^^]\\\\\\\\\\\]\[[[[[[[[[[\[ZYVSPMKLOSX]_ZTPKJNTYXVTUXZYW_]QB4.)*6HW]XIGH>51/2;@?75;@DEIJD:1+'# !$&$,653130&"(+033467?KOJ>9<ACB>7,((*(!".BNRSSSSSQQQQSTSRRRRQQQRRQQQQQQQRRRRQPPOOOOOOOOOPPPOOOOOPONMMMNONMLMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKJJJJJJJIIIIIIIIIIHHHHHHHHHHHHIHGGGGGGGGGGFFGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEDCCCCCCCCCCBBCCCBBBBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@@????????>>>>>?>>>>>>>>>>>>>>>>>>>>>>>>>==<<<<<<<<<<<=<<<=<<<<<<<KKJJJIIIJJJKKKKKKKKJJJJJJKKKKKLLLLMMMMLLLLLMMMMNNNNNONNNNMMMMLLLMNNNNNNOOOOPPPPOOOOOPPPPQQPPPPQQQQRRRRSSRRQPPQRRRRQRSSSSSSSSSSSSSSSSSSSTTTTTTTTUUUUUUUTUUUUUUUTUUUUUUUVVVVVVVWWWWWWWWWWWXXXYYYYYYYYYYYYYYYYYYXXYYYYYYYYYZYYYZZZZZZZZZZ[[[[[[[[[[[[[\\\\\\\\\\\]]\\\\\\]]]]]]]\\\\\\\\\\\\\\]]]]]]]]]]^^]^^^___^^^^]^_``aa`````a`````````````___``````````aaaaa``aaa``a``aba`]XRH<0&#"#" !%)--& !#-/+$!$),+,-++,2660*'(-37641+())&!"!!%" !&%$&/@Udjkjjjjjjjjjjjjjjiiiiiihhhjjiiiiiiiiiiihhhhhhhhhhhhhhghgggggggghhhhhggggghhhhhgggggggfggfggggfgggggggfffgggggggggggffffffffffffffffffeedddcccddddddddddddddddddddddddddccbbbbbbbbbbbbbbbbbbbabaaabbaaaaaaaaaabaaaa```````a_^^^^^^______^^]]]]^^^^^]]\\\\\\\\\\\\\[[[[[[[[[[\\XSQONNPSUVVVSLD@;<BKTVWVXZZYY\R=*!##%0@QXSB95*$%)1::4*-;DE=<??8,(&# !&'#'.*'(-.$#&(,/0/-3?B=1/6B?.+*'*03-+?OQRTTSRQPPPQQQQQQQRRRRRRRQQQQRQQQPPOOOONOOOOOPPPPPPOPPPNMMMNNNMMMMMMMMLLLLLLLMLLLLLLLLLLKKKKKKKKJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGFFGHGGGFFFFFFFFFFFFFFEEEEEEEEEEEEFFFEEEEECCCCCBBBBBBBCCCBCCBBBBBBBBBBBBBBBAAAAAA@@@@@@@@@@?????????>>>>>>??>>>>>>>>>>>>>>>>>>>>>>>>=<<<<<<<<<<<<<<<<<<<<<<<;KKKKKKKKKKKJJKKKKKJKKKKKLLKKKKLLLLMMMMMMMMMMMMNNNNNMMNNNNMMMMMMMNNNNNNOOOOOPPPOOOOOOOPPOOQRQQQQPPQQQQQRRRRRRRRRRRRRRRRRSSSTTSSSSQQRRSSTTTSSRRSTUUUUUUUUTUUUUUTTUUUUUUUVVVVVVWWWWWWWWWXXXXXXXXXXYXXYYYYYYYYYZYXXYYYYYYYYYYYYZZZZZZZZZZZZZZZ[[[[[[[[[[[[\\]]\\\\]]\\\\\\\\\\\\\\\\\\\]]]]^_^]]]]]]]]]^^^^^^^^^^_____^^^^^^^________``````````````````````````aaa__``_^_bbbcdb\TJ=/# "! # #*,*'$ "!!(,+'%%'(*,/0*-:B@4-+-/19>92-++-*$! !" !$"*<Remnkhgjkjjjjjiiiiiiiiiiihiihhiiiiihhhhhhhhhhhhhggggiihgggggggghhhhhgggghhiiihgggggggggggggggggggggggffggggffffffggffffffffffffeeeeeeeeeeeeeedddddddddddddcccccccccccccddddededdccccbbbbbbbbbbbabbbbbaaaaaababbba````````a_^____________^^]]^^_^]\\\\]]\\\]_`^[WX[[[\\]^_^^]\[XQG@ADFB=:3,)+237=ELSUSTX^_YG5'$# &4EMI:0+(')2;;;9/-3?FE>4-(&&&" "# $&&&$! #%&&'()(&%&'&$#$''$#"#$&)*(#!$/=FOVUSSTUSRSSRPPRUTQNNRUVTRQOMLKLLLMQRNNOOONNOQPNKKNNOPPOMLLMNONLLMMMMMMMLLKKKKKLLLLLLKKKKKKLLLLLKKJJJJJJJJJJJIIIIHHHIIIIIIIIHHGGGGGGGGGGGGGFFFFFFFGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEDCCCCCBBBBBBBCCCCCCCCBBBBBBBBBBBAAAAAAAAAA@AAAA@@??????????????????>>>>>>>>>>>>>>>>>>=>>>>=<<========<<<<<<<<<<<<<;;LKKKKKKKKKKKKKKKKJJKKKKLLLKKKLLLLLMMMMMMMMMMMNNNNNNMMNNNNNNNNNNNNOOOOOOPPOOOOPPPPPPPPPPPPQRQQQQPPQQQQQQRRRRRRRRRRRRRRRRSSSSSSSSSQRRSSTTTTTSSSTTUUUUUTUUUUUUTUUUUUUUUUUVVVVVVVWWWWWWWXXXXXXXXXXYYXXYYYYYYYYYYYXYYYYYYYYYYYYZZZZZZZZZZZZZZZZ[[\\\\\\[[[\\]]]\\\\]]]]]]]]]]]]]]]]]]]]]]]]^^_^]]]]]]^^^^^^^^^^^^^______^^^^^^^______```````````````_```aaaaaa`_aaa_^`aa__`abcaZOE>6,! ! */,&!#$%&%#&**))*+++,.147;==:50,,/4::2*&(,-(! "!*>Tckkigiiiiihijijjjiiiiiiiiiihiiiiihggghhhhhhhhhggfhiihhhhhhhhhhhhhhhhghhhiiihggggggghhhhggggggggggffffghhgffffffggfffffffffeeeeeeeeeeeeeeeeeddddddddddddddcdddddddddcdddddeeeeddccccccbbbbbbbbbbbbbaaaaaaaaabbba``a``````_____________^_______^]]]\[\]]\\\\]\[YXY\__\WQICCDHSWL>1--/.,,(#%*/138=CMTTWXUSL9( #" &1;B@3*''*/8;1./+.6AHD7)"!$%$! !%%#! #&&%# !!#%'#&4;ENQRQQQOOQRRRSSSQPRTUTPLIHDA?@ABDFKOQPOLKKLMNMLKLOOOONMNNNNNOOMMMLLMMMMLLLLKKLLLLLLLKKKKKKLLLLLKKJJJJJJJJJJJIIIIIIIHIIIIIIIHHHGGGGGGGGGGGGGFFFFFFFFFFFFGFFFFFFFFFFEFFFEEEEEEEEEDDDCCCCBBCCBCCCCCCCDCCCBBBBBBBBBBAAAAAAAAAAAAAAA@@????@@@???????????>>>>>>>>>=>>>>>>>=======<<========<=<<<<<<<=<;<;;LLKKKKKKKKKKKKKKJJJKKKLLLLKKLLLLLLMMMMMMMMMMMNNNNNNNNNNNNNNNNNNOONNNNOOOOOOOPPPPPPPPPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRSSSSSSSSSRSSTTUUUUTTTTUUTTTTTUUUVVUUUUUUUVVUUUUVVVVVVWWWWWWWXXXXXXXXYYYYYXXYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[\\\\\\[[\\]]]]\\\\\\]]]]]]]]]]]]]]]]]]]]]]^^^]]]]]]]^^^^^^^^^^^^^______^^^__________`aaaaaaaaaaa``_```aa````a`_aba_^`cca_^_`aZM?8983& ! ! (/0)"#'*(()(&&),....0358DD:4385.,1882*$!$()%""" *=Qbikihhhihfhjjjjjjiiiiiiijjiiiiiihhgghhhhhhhhhhhhhiihiiiihhhhhhhhhhhhhhhiiihgggggggggggghhhggggggggggggggggggggffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddcccccccbbbbbbbbbbbbaaaaaaaaaaaaaa`aa````______________^^____^^^^^_^[[\\\[ZZ[[\\[Y[ZWOD9-&%(.?H>2&$).00001123459HSVWRF@;/&##"!+1165,'%%)087'!"$,7@B9-!%$! #$# !$%# !""#.3;DIJGEEEGKMPRRQMLNRTPIBA?=964467:=AHQRMKKKLLKJJKMNONKJKMPQOMNNONMLLLLLMMMMMLLLLLLLLLKKKKKKKLLKKKKJJJJJJJJJJJJIIIIIIIHHHHHHHHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDDDDCCCCBCCCCCCCCCCCCCBCCCCBBBBBBAAAAAAAAAAAAAAA@@@??@@@@@???>>>>>??>>>>>>>>>>>>>>==========<<=====>==<<<<<<<<<<<<<;;LLLKKKKKKKKKKJJJJJJKKLLMMLLLLLLLLLLLLLLMMMMMNNNNNNNNNNNNNNNNNNONNNNNOOOOOOOOPPPPPPPPPPPPPQQQQQQQQQQQQQRSSSSSSSSSSSSSSSSSSSSSSTTTTTTTUUUUUUTTTTTTTTTTUUUUUUUUUUUUVVVVVVVVVVVWWWWWWWWXXXXXYYYYYYYYXXYYYYYYYYYZZYYYYYYYYYYZZZZZZZZZZZZZZZZZZ[[\\\\\\\\\\\]]]]\\\\\\]]]]]]]]]]]]]]]]]]^^^^]]]]]]]]^^^^^^^^^^^^^_______________````_``aaaaaaaaaaa``_```aaaaaa`abcb`_`bcbba`a`[OA53991# "!!""" '00+%#%(**'%%" "',--,/5;>>A?98885248<6.)$!!#$"$##" ! "" (?Udkljhhkkhjkkjjjjjjjjjjkkkjjjjiihgghhhhhhhhhhhhiiiiiiiiiihhhhiiiihhhhhhhhhhgggggggggggghhhhggggggggggggggggfgggffffffffeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddccccccccccbbbbbbbbbbbaaaaaaaaaaaaaaa`a`````_____________^^^^][[\\]^__^]\[\\]^_^^\\ZSH=3+&$#"""*.*%!$.8?>>;1-0573-/BQRKA;4.%"$" #!!+-')*&$# #*46)"!"'.32,#"!!#""# "# "'.8@@;3259?DILLJIHIKJE=52;?:51.-/0259@JNNOQQPOLIHHHIJJHHJNPPNLLMNMLKKKLLMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIHHHHHHHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFGGGGFFEEEFFEEEEEEEEEDDDDDCCCCBBCCCCCCCCCCCBBBBBBBBBBBBBAAAAAABBAAA@@@@@@@@@@@@?????>>>??>>>>>>>>>==========<<<<<<<=======<<<<<<<<<<<<<<<<LLLLKKKKKKKKKJJJJJKKKLLMMMLMLLLLLLLLLLLMMMMNNNNNNNNNNNNNNNNNNOOONNNOOOOOOOOOPPPPPPPPPPPPPPPQQQQRRRRQQQRSSSSSSSSSSSSSSSSSSSSSSTTTTTTUUUUUUUUUUUTTTUUUUUUUUVVVVVVVVWWWWWWWWWWWWWWWWWWXXXYYYYYYYYYYXXYYYYYYYYYZZYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[[\]]]]]]\\\]^^]]\\\\\]]]]]]]]]]]]]]]]^^^^^^^]]]]]]]]^^^^^^__^^^^__________```___```````a`````````a````aa``aaaaacdca^`bca_`bde`TE84594) "!"%&##*03+%$).+(&%# %)+-/6>EHF<8<=9/08@C>5.,($"!!!%&"!#!!""" "#! "!-CXholiilmjjkkkkkkkjjjjjkkkkkjjjiiihhiiiiihhhhiiiiiiijjjjjiiihiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggfgfeffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccbbbbbbbbaaaaaaaaaaaaaaaa`a``____``_________aa`_]\\\]]\[[^^]]\]^__]YVOF8-),39?B?91*&()))-5BIFGF<66;<4(%9ILA66/%"! !#%""" !!#-2-)&$####!! "&(&## (284,()/5<DKMIFDEFD<2*')++(%!!#%(+/4:@GMQSQOOPQQQMFEEEHKNOONNPOLLLLLLLMNMMLMMMMLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIHHIIIIIHHHHHHHHHHHHHHGGGGGGFFFEEEEEFFGGGGFFEEEFFEEEEEEEEEDDDDDCCCCCCCCCCCCCCDCCBBBBBBAAABBBBBAAAABBBAA@@@@@@@@@@@@@?????????>>>>>>>>>>==========<<<<<=<========<=====<<<<<<<<<LLLLLKKKKKKKKJJJJKKKKLLMNMMMMLLLLLLLLLMMMMNNNNNNNNNNNNNNNNNNOOOOOOOOPOOOOOPPPPPPPPPPPPPPPPPQQQRRRRRRQQRSSSSSSSSSSSSSSSSSSSSSSTTUTTUUUUVVVVVUUUUUUUVVUUVVVVVVVVVWWWWWWWWWWWWWWWWWWWWXXYYYYYYYYYZZXXYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[\\\\]]]]]]\]]^^^]\\\\\\]]]]]]]]]]]]]]^^^^^^^^^]]]]]]]^^^^^____^^__________`````````aaaa````````````aaaa`aaaaaaaabcb_]_aba_`bbc\K<3221) ##$''&"$053)%(/1)#"%$!%*.39AGJKF=;@=5,3AGC72/-*'%#"%*&!"%! "! ! ! !#-E^mmjglmjjkkkkkkkkjjjjjjjkjjjjjijiijjijiihhhiiiijijjjjjjjiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiihhhhhhhhhgggggggggggffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccccbbbbaaaaaabaaababa````___````___________^]]]^__]ZY\^__][[[[VPKB6(%,=KOPMIA7+$')+,08FOJIIF?89:5'.=D=40'! "$"! "'*(('&# &**($##*,)')-139AILHC>=:7.% $#! !$(,04<EKNLJJMNMJFC?=@EKKKJLOQOLMMMMNMMMMLLMNMMMLLLLLLLLLKKKKKKKKKKKJJJJJKKJJJJIIIIIIJIIHHIIIIHHHHHHHHHHHHHHHHGGGGFFFEEEEEFFGGGGFFEDDFFEEEEEEEEEDDEDDDDCCCCCCDDCCCCCCCBBBCBBBBBBBBBBBAAABBBAA@@@@@@@@@@@@???????????>>>>>>>>>========<<<<<<<<<========<<<<<<<====<=<<LLLLLLKKKKKKKJJJJKKKKLMMNMMMMMMMMMMLLLMNNNNNNNNNNONNNNNNNNOOOONOOOOOOOOOOPPPPPPPPPPPPPPPPPPQQQQRRRRRRRSTTTTTTTTTTTTTSSSSSSSSTTTTTTUTUUUUUUUUUUVUUUUUUUVVVVVVVVWWWWWWWWWWWWWWWWWWWWWXXYYYYYYYYYYYXXYYYYYYYYYYYYYYYYYZZZZZZZ[[[[[[[[[[[[[[[\\\\]]]]]]\\]^^^^]\\\\\]]]]]]]]]]]]]^^^_____^^^]^^^^^^^^^^_____________`````````aaaaaaaaaaaaaaaaaaaaaaaaabbbaabba`a`]^`bcbb_ZXQA71.*$##!"%&%#!&361++/1,"!%'%%%'*/5=DIKIFBEE@5.3>HE:-.0-*))&"!()# "%%!"""!"#! !! "!1Ogljgjkhjllkkkkkkkkkkjjjjjjjjjjjjjkkjjjiihhiiiijjjjjjjjjjiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiihhhhhhhhhggggggggggfffffffffeeeeeeeffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeedddddddcccccddddccccccccccccbbbaaaaabbaaaaaaaaa``___`````______^ZZ]^^^^_``_]\\^`_\YWVUPIC9/(.@S\ULB=82+%%%&',3CMIGFJD5112'%1<?8( "#&%$&'%""%%#! $&&$" "&)(%! !!"'044/19@DB=60+'# ""! "&)/7<????@?<77;516?GHGGKNNNNNOOONNMMMMLMMMMMLLLLLLLMLLKKKKKKKKKKKJJJKKKKJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGFFFEEEEEFFGGGGFFEDDFFEEEEEEEEEDDEEDDDCCCCCCDDCCCCCCCBCCCCCCCBBBBBBBBBBBBBAAA@@@@@AA??????@@@@@@@??>>>>>>>>>==========<<<<<<<========<<<<<<==>===<<<LLLLLLLLKKKKKKKKKKKLLLLMNMMMMMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPQQQQQRRRRRRRRRSSSSSSSSSSSSTTSSSSSTTTTTTTTUUUUUUUUUUUVVUUUUVVVVVVVVVVVWWWWWWWWXXXXXWWWWWWWWXXXXXYYYYYYYYXXYYYYYYYYYYYYYYYYZZZZZYYZZZZZZZZZZZZZ[[[\\\\]]]]]]\\]^^^^]\\\]]]]]]]]]]]]]]]^^^_________^^^^^^^^^^^^^^________``````````aaaaaaaaaaaaaaaaaaaaaaaaaaaaabbba`cc``aceecYLC<740)!"!" "" ")660/03-$##$(+-/039?CDDCBBLH7+->HH=1+,//.,)# %'%!"%# "$&)(%$# ! ! $@[gkijjhjllllkkkkkkkkjjjjjjjjjkkkkkkkjjjiiiiiiijjjjjjjjjjiiiiiiiiiiiihhhiiiiiiiiiiiiiiiiiihhhhhhhhggggggggggffffgffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccbbbbaaaabbbbaaaaaaaa`````a``````_____^_aa_]\[]^__`ba^[XVWXVQJC6*+6EOOD=:=<72-)%"$&'5ABCDGB5,(+#+9@8!&.3531+&%$#" !%&#"%&#! ! ! !#"! !" #,0.('+0330*$ !&'&%&'*,.///,'! (6BGJLOPONNOOPONMLLLMMMLLLLLLLLLLLMLLLKKKKKKKKKKJJKKKKKKJJJJIIIIIIIIIHHIIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFGGGGFFEEEFFEEEEEEEEEDDEEEDDDCCDDDDDCCCCCCCBCCCCCCCBBBBBBBBBBBBBAAAAAAAAAA?????@@@@@@@@??>>>>>>>>>==========<<<<=<<========<<<<<<======<<<LLLLLLLLLKKKKLKKKKKLLLLMMMLLLLMMMMMMMMNNNNNNNNNOOOONNNNNOOOOOONOOOOOOOPPPPPPPPPPPPPPPPPPPQQQQQQRRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVVVVVVVWWWWWWWWXXXXXXWWWWWWWXXXXXXXXYYYYYXXYYYYYYYYYZZYYYYZZZZZZZZZ[[ZZZZZZZZZZZ[[\\\\]]]]\]\\]^^^^]]]]]]]]]]]]]]]]]]]^^^^^^_______^^^^^^^^^^^^^_______```````````aaaaaaaaaaaaaaaaaa`aabaaaaabbbbbaaefbaaa_\XK;/*,.-$!$" #+65./0.'! #&#%,02358=>=<=AGKJ>*%-?FB9441/0/,% %'% #$ "&)**(''%! ! /G\hkkjikllkkkkkkkkkkjjjjjjjjjkkkkjjkjjjjjjjjjjjjjjjjjjjiiiiiiiiiijiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhgghhhhhgggggggfffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeddddddddddddddddddcccccccbbbbaabbbbabaa`aa``````aa`````_____^_a`_]]^abcc_WRLIGGIIGC=92,19>>943574.+)))*/-&-7<@CIH?/$$)6;5% $(,,+(% ! "$!!"! !%&"!#%'&$" &%! #$$$$%%$"#(2<BEGFEBCGIKMNNLLKLMNMKLKKLLLLLLLLLLLLKKKJJKKKKJJKKKKKKJJJJIIIIIIIJIIIHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDDEEEDDDDDDDDDDCCCCCCCBCCCCCCCCCBBBBBBBBBBBBBAAAAA@@@@@??@@@@@@?????>>>>>>>>>==>>>==========<<========<<<<<<=========LLLLLLLLLLKKKLLKKKKLLLLMMLLLLLLMMMMNNNNNNNNNNOOOOOONNNNONNNNNNOOOOOOOOPPPPPPPPPPPPPPPPPPPQQQQQQQRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTSTUUUUUUUUUVVVVVVVVVVVVVVVVVVVVVVWWWWWWWXXXXXXXWWWWWWWXXXXXXXXYYYYXXYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZZ[[[[\\\]]\\\\\]^^^^]]]]]]]]]]]]]]]]]]]^^^^^^____```______^^^^^^^______`````````````aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbeeab`[SJD8*! $'' !"! %+11-.,&! #&((,35436<BB<8:ENP@/%(3>@=99;60/+& $'% #%$!$*--+($"!2Odlkjkkllkkkkkkkkkkjjjjjjjjjjkkjiiijjjklkkkkkjjjjjjiiiiiiihhhiijjjiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhgggggggghggggggggfffffffffffffffffffffffffffffffeeeeeeeeeeeeefeeeeddddddeeddddddddddccccccbbbaabbbbbbaa`a```__```aaa``_`_____^___^]_ab`[UNG@<9:<;60./2337850.14640-+**)+43(,5<>?EHE1!"! '11,'$$$" #&%#!!!"!" ! "" !! #(,-,,.3786436;?DILMLKKKLMLKKKKLLLLLLLLLLLLKKJJJJKKKKKKKKKKKJJJJJJIIIIIIIIIIIHHHHHHHHHHHHHGFFFFFFFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDEEEEDDDDDDDDEDCCCCCCBBBCCCCCCCCCBBBBBBBABBBBBAAA@@?@AAA@@@@@@??????>>>>>>>>>=>>>>>>>=======<<=====>===<<<<===>======MLLLLLLLLLLKKLLLLLKLLLLMMLLLLLLMMMNNNNNNNNNMNNNNNNNNNNNNNNNNNNOOOOOOOOPPPPPPPQQQQQQQQQQQQQQQQQQQRRRSSTSSSSSSSSSSSSSSSSSTTUUUTTSTUVUUUUUUUVVVVVVVVVVVVVVVWWVVVVVVWWWWWWXXXXXXXWWWWWWWWXXXXXXXXXYYXXYYYYYYYYYZYYYZZZZZZZZZZ[[[[[[[[[[[[[[[Z[[\\\\\\\\\[\]^^^]]]]^^^^^^^^^^^^^^^^^^^^^____`````____^^^]]]^______`````````````aaaa`aaaaaaaaaaaaaaabaaaabbbbbbbbba_`]TE70' "#'*)*,-(!%'(08=:515?GG?;>KPI2&)4=>>=;9873+#"%%!"%# #'+($!!" #%&%" !"! %$!A[iiilllkkkkkkkkkkkkkkjjiiiiijjjihhijkkllllllkjjjjiiiiiijihhiijjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhggfffghhgggffggggggffffffffffffffffffffffffffffffffffffffffffffeedddddeeeeddddddddddddcccbbbabbbbbbbba`a``````aaaaa````_____`a_^^^__\SF96=<:;<;3) !(266971.1:<:44994-$!-0)/9@?;;>?- &)*$$)'$$" "%#$+-(" "##" ""!!! " !"!$)01*#!$&%&),05<CHKLLKKKLJIJKKLLLMMLLLLLLKKJJJJKKKKKKKKKKKKJJJJJJJIIIIIIIIIIHHHHHHHHHHHIIGFFFFFFGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEDEFEEEEDDDDDDEDCCCCCCCBBBBBBBBBBCBBBBBBBBBBBBBBBAA@@@ABBA@@@@??>>>??>>>>>>>>>=>>>>>>>=======<<========<<<<<<=========KKKKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMMMMLLLMMMMMMNNNNNNNNNNOOOOOOOOOOOOOOOPPQQQQRRQQQQQQRRRRSSSSSSSSSSSSSSSTSSSSTTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWWWWWWWWXXXXXXXWWWWXXXYYYYYYYYYYZYZZ[[YYYYYZZZZZZZ[[[[[[[[[[[Z[[[[[[\]\\[\[[\]\\]]\\\\]^^]]]\]]]]]\]]]]^^^__^^^__`_]]^^^^^^^^]^_``_______``````````aa```aaaa`````aaaabcccbbabbbbbbccbab_TA4+$!" "&*+)$',0)%/.*4?D8/5?D=528DPH6-*/9ACB@=;94-&!"'(%#"#!!&+)&%&(&#! ,DZfjijkmmkiiklkiiijjjjjjjkkkkkkjijjjiijjjjkkkjjjjjjjkkkkkjjiiiiiiiihhhhhhhhiiiiiiiiiiiiiihhhhhhhhhhggggghhhhgffeeeeffggfffgffffeeefffeeeeddddeefffffffffeeeeeeefffeeeeeeeeeffeeddddddddedbcccccaabbbbbaaaaaa`````aba_ab`ab``b_^a`^^_ca\VC12;GQWVH4($()+.,+8<4,,6;;868;6%!&-.*)/;@?=942/+(& "+.,'#"# ',+% !#" #%%$#" "(-,-1:BFHHIIHEDHKLLMNMLKMNKIJJKLLLLLLLLLLLKKJJJJJJJJJJIIIIIIIJIIIIIHHHHHHHIHHHGGGGGGGGGGHGGGFFFFFFFFFFEEEFFFFFFFEEDDDDEEDCCDDDDCCCCCCDDDDDCCCBBBBBCBBBBBBBAAAAAAABBAAAA@@@@@@@@???>>?????????????>>>>>=================<<<<<<<<========KKKKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMMMMLLLLMMMMNNMNNNNNNNNNOOOOOOOOOOOOOOPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUTTTTUUUUTUUUUUUUUUUVVUUVVVVVWWWWWWWWWWWWWWWWWWWXWWWWWWXXXYXXXXXWWXXXXYYYYYYYYYYYZZ[[[YXYYYZZZ[[[[[[[[[[[[[[[[[[[\[[\\\\[[[[\]\\\\\\\]]]^^]\\]]^]\\\]^]^____^^^__`_^]^^^^^^^^^__``_______```````aaaaaaaaaaaa`````aabbbcccbbbaaabbbccace_O7)&"!"$# ! !!!%*,-(%%*/-&!(.//8>?74?FF7.0=IK<*-39>@@@A?;61*$"" #(*(%"!&*+*'&&&" 0H]iiijlmljiklkiijjjjjjjkkkkkkkjjjjjiiijjjjkkjjjjjjkkkklkjjjjiiiiiihiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhggggghhihgffeeeeffgggggffffeeeefffeeeedddddefffffffffeeeeeeefffeeeeeeeefffeeddddddddedbcccccbaabbbbaaaaaa`aaa`acc`cd``a__b_]`aabca[RK<3@KPSRM?.&(264,)+791+*179:::=9+)-+))(-8?@>7010'#"!+-,'" "" ),*#!"! "$$#! #+-,,2;DIJJIGEDIKLMNOMKKLNJIJJJKKLLLKKLLLLKKJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHIHHHHHHHHHHHHHHHGFFFFFFFFFFEEEFFFFGFFFFEDDCDEEDCCCDDDBBCCCCDDDDDCCCBBBBBBCCCBBBBAAAAAAABBBBBB@@@@@@@@??????????????????>>>>>===<<<<======<===================KKKKKKKKKKKKKKKKKLLLLMMMMMMMMMMMMMMMMMMNNMMMMMMMNNNNNNNOOOOOOOOOOPOOOOOOOOOOPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVUUVVVVVVWWWWWWWWWWWWWWWWWXXXXXXXXXXYYYXXXXXXXXXXYYYYYYYYYYYYZZZZYYYZZZZ[[Z[[[\\[[[[[[[[[[\\[[[[[[[[\\\\\\\\\\\]]]]]^]\\]^^^]\]^^^^_`_______``___________````_______```aaaaaabbbbbbbbbbaaaababbbccccbbbbbbbbbbbbdf^L2%#$&&$"!#"!#(/0-%"*01'!"(,,05::77;DGB508EJ@2*4>CB><=@?92/+$#%%$#'**% !)+*'()'# # "# !5Pcijijkklkkkjjjkjjjjjkkkkkkkkjjjjjjjjjjjjjkjjjjjkkkkkkkjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhggggghhhhhgffffffggfggffffeeeeeeffeeeeeeeeeefffffffffeeeeeeefffffeeeeeeeffeeedddddddedbcccccbbbbbbbbbbbaaaaaaaaccacc``a^^b`_`acfbYQH@<?MRME<62/,/8?=.,02/*)+0469:898798-&&)/5;?>81.*! ")'$$! !"!!#)+(" *36448@GJJIIIJKLMMNMMLLMMJIJJJIIJKLLLKLLLKKJJJJJJJJJJIIIIIIIIIHIIIHIIIIIIHHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDDCBCDDDCCDDDDDDDDCCCBCCCCBBBBBBBBBAAAAAAAAAAABA@@@@@@@@??????????????????>>>>>====<<<======<<==================KKKKKKKKKKKKKKKKLLLLLMMMMMMMMMMMMMMMMMMNNNNMMMMNNNNNONNNNNOOOOOOOOOOOOOOOOOOPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTTTTTTTTUUUUUUUUUUVVUUUUVVVVWWWWWWWWWWWWWWWWWXXXXYYYYYYYYYXXXXXXXXXXYYYZYYYYYYYYYZZZZZZZZ[[[[Z[[\\\\\\\\\\\[[\\\[\\\\[[\\\\\\]]]]]]]]]]^^]]]^^^]]]^^____``____```````````aaaa`````____````abaaaabbbbbbbbbbbbbbbbcccccccbbbbbbbbbbadfeZF-""$&&# #$" !$)/0+&%.0+!&/557:<;98:??;34>FD627@FGC?>?@;5/-)#"%(('()'$!!&.,(##%# ! #$!%B[gkjjjkmmkjijklkkkkkkkkkkkkkjjjjjjjjjjjjjjkkkkkkllllkkjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhggggghhhhggfffffgggggfffffeeeeeeeeeeeeeefffffffeeeefffffffffgfffeeeeeeeeffeeedddddddedcccddcbbbbbcccbbbbaaaaaaa`^]_a`abaacdc```aZNID99?EE?83269536>?4/.($"'.24552-,1>HI7)%',.7>?:30+"! #'!#"!!! "$'(% "! " )9B@;78<AFIJMNMMMMMLLMNONIHJKKJJKMNNNLKKKKKKKKJJJJJJJIIIIIIIIIHIIIHHHHHHHHHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDCBBCCDCBCDDDDDDDCCCBBCCCBBCBBBBBBBAAAAABAAAAAAA@@@@@@@@@@????????????????>>>>>=====<<======<<<<<<<<<<<<<<<<<<<<KKKKKKKKKKKKKLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNMMMMNNNNNNNOOOOOOOOOOOOOOOOOOOOOOPPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVUUUUVVVVVWWWWWWWWWWWWWWWWXXXXXYYYYYYYYYYYXXXXXXYYYYZZYYYYYYYYYZZZYZZZ[[[[[[\\[[[[[[[[[[[[\\\\[[[[\\\\]\\]]]^^^^^]]]^]\\]^^^]\]^^^__```____```````````aaaa`````____````abaabbbbbbbbbccccccccccccccccbbbbbbbbbbcfe_Q>*!"""!""!##&+/.(),2,#"&-6=@=8:<=:8;856=EB:4<GFA=>AFE>500-)%$&*./,($#&*,*&#! !#""!!" !"!##!!"$%%$#"! 4N`jlkjknnljiklkkkkkkkkkkkkkkjjjjkkkkjjjjijkkkkllllllkjjjjjjjkkkkkkkjjjjjjjiiiiiiiiiiiiiiihhhhhhhhggghhhhhhggfffggggggfeffeeeeeeeeeeeeeefffffffeeeeeffffffffggffffeeeeeeeefeeeeeeeeddddccddddcccccdddccbbbbabbcba_^`bcdca__^]\ZXYTKLH88ABBA=846:73012--.&!!&-034786..>MR@0()+),231+*'!!#""$%$"! !##"!!%" #""4AE@823;EKJMNLKKLLKKLLLKIJLMMLKLMMMKJKKKKKKKKJJJJJJJIIIIIIIIIIIIIHHHHHHHIHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDCBBCCCCBCDDDDDCCCCCBBCDCCBBBBBBBBBBBBBBBAAAAAAA@@@@@@@@@@????????????????>>>>>>============<<<<<<<<<<<<<<<<<<<<KKKKKKKKKKKKLLLLLLLMMMMMMMMMMMMMMMNNNNNOONNNNNNNNOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTTUUUUUUUUUUVVUUUVVVVVWWWWWWWWWWWWWWWWWXXXXXXYYYYYYYYYYYXXXXYYYYZZZZYYYYYYYYYZZZZZ[ZZZZ[[[[[\[[[[[[[[[[\\\\\[[\\\\]]]]]]]^^^^^]]]^]\\]^^^\\\^^]^__`__^^_```````````aaaaa```````````aaaaaabbbbbbbbccccccccccccccccccccccccbcdhcVE4'!" !$$)01,&+13*!)28>AA:38=>==>:6=EG=6>KSG<9@ILF<301,(()*.24/)$&+.+#!!" ! #&&#$$ #%" ""! !$#! """! !#""!!!! '?Wfnlkkmnljjlkjkkkkkkkkkkkkkjjjkkkkkjjjiijjkkkllllllkjjjjjjjkkkkkkkkjjjjjjjijiiijjiiiiiiiiiiihhhhhhhhhhiihhhgggghhhggfffeeeeeeeeeeeeeefffffffeeeeefffffffffgffffffffffeeeeeeeeeeeeedddcdddddccccdddcccbbbaaabbb`_^_aceda^[YY[ZWWSKLE/1?B?:8511364.)')+,(&'),-.16=A2)6FPE6))*''(('$%&##$! !'& #!!"#&!,9?>947?IMJLNLLLMMMLLKHFHJLMMLLLLLLIHJKKKKKKKKJJIJJJIIIIIIIIIIIIIHHIIIIIIHHHHHHHHHHHHGGGGFFFFFFFFFFEEEEFFFGFFFFEDDCDDDCBBCDDCCCCCCCCCCCCCCCDDDCCBBBBBBBBBBBBBBAAAAAAA@@@@@@@@@?????????????????>>>>>>>>==========<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMNNNNNNNOONNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUUTUUUUUUUUUUVVVUVVVVVWWWWWWWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYYZZZZZZYYYYYYYZZZ[[[[[[[[[[Z[[[[[[[[[[[[[[\\\\\\\\\\]]]]]]^^^^^^^^^^^]\\]^^^\\\^^]]^___^^^^_```````__``a```aa````````aaaaaaaaaabbbbbbbbbbbbbbbbbbbbbccccccccccdg^J8*$! "!!"$%-53,%,31,)3>FD=5//:?=BGEA>FID::NWTD<ALRLC:642*&,02454/+)+-+&!# !!!"#$&'% "!&&# %&$ #%$" ! #&&%%$$#"!! 0Kannmkmmmkklkikllkkkkkkkkjjkkkkkkkkjjjiijkkkkllllllkjjjjjjjkkkkkkkkkjjjjjjjjjijjjjiiiiiiiiiiihhhhhhhhhiihhhhhhhhhhggggfffeeeeeeefffffggggfffeeeeffffffffggffffffffffffeeeeeeeeeeeeedddddeedcccccdcbbbaa```_```_]\\^bdc`\XWX]^]ZQFB:&*;@9+)+*+-462*%**(),--,+*,08@3',:HE8&$&''&$""$''&$ (&!" "!#% (/247:?EKKFILLLMOONNLJGFHJKLLKKKLLKJJJJKKKKKKKKJIJJJIIIIIIIIIIIIIIIIIIIIIHHHIIHHHHHHHGGGGGFFFFFFFFFEEEEFFFGFFFFEDDCDDDDBBCDDCCCCCCCCCCCCDDDDDCCCCBBBBBBCBABBBBAAAAAAA@@@AAA@@@@????????????????>>>>>>>>==========<<==================LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNOONNNNNNNNONNOOPOOOOOOOPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZYYYYYYZZZZ[[[[[[[[[[[Z[[[[[[[[[[[[[\\\\\\\]]]]]^]]^^^^^^^^^^^^]\\]^^^\\\^^]]^___^^^^___``````````aaaaa````````aaaaaaaaaabbbbbbbbbbccbbbbbbbbbccccccccccbcV?-" !" !$&(,250)%-2,/5;ENF6%&3?DBHKDBDJH>;DZWF>BOWUE;9<9/&$-4873.+,..+&"!!!!!$&%####" #'''&%$$$%&&&$! ##! !! !!"$&''&&%$"! "#""! '>Winnkllmllmkiklllkkkkkkjjjkkkkkkkkjjjjjjjkkkklllllkjjjjjjjkkkkkkjjjjjjjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhiiiihhhhhhhhggffgfffeeefffffgffggggfffffefffffffffggfffggffgffffeeeefffffeeeedddeeeddccbbcca````___^^^^^aa^\\``^[WSTWZ^^TC201,4=7,"#(/11451)$)%#(++,.-+()..,*3?A5 #&'&%$&()'# &$!" "#! ## (,+'',49;<;;BHHIIHJLLKJKLLLLLLLLLLLMMMKJJKKKKKKKJJJJJIIIIIIIIIIIIIHIIIIIIIHHHIIIHHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDCDEEDCCCDDDCBBBBBBCCCCDDDDCCCCCBBBBBCBBABBBBAAAAAAA@@AAAAAA@@@???????????????>>>>>>>>>>>>>=====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNOOONNNNNNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXXXXYYYZZYYYYYYYZZZZZZZZZZZZZZ[[[ZZZZZZZZZZZZ[[[[[[[[[[[[[[\\\\]]]]^]^^^^^^^^^^^^^^^_^]]^^_^]]]^^^^_____^^^___``````````aaabaaaaaa`aaabbbbbbbbbcccccccccccccbbbaabbbccccddcccca]L4&!"#!""" &)-242*').0*2?ADH>0#*;BGHJH?@FIE>CNZL8;GSTM>8:>9-'(06750++-0-'##%$#!!$(**)'$" $),.+%$$%&&&&('&" !!#$###"""#$%'''(())'%#""!"$&%%$#""#$#"! "" "1Jalolkkllmnlikllllkkkkkjjjkkkkkkkjjjjjjjjjjjjkkkkkkkjjjjjjjjjjkkjiijjjjjjjjjjjjjjjjiiiiiiiiiihhhhhhhiiiiiihhiiiihgggggffffeeffffggggggffffffffffffffffffffffgggggffffeeeefffffeeeeddeeeeedcbbbbca`````___^^]]^^\ZY]\YVSQUZ\`^J4%'08><+$+-/243431+(+' !"$%',.-'$&()%'-3.!!&**%"###$"#"""#" !! #*+& ')'&(+4<>?>=?CEDDFJMNMLLMMLKKKLLKKKKKKKKKKKKKJJIIIIIIIJIIIIIHHHHHHHIHHHIHHHHGHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDDEEDCCDEDDCCCCCCCBCCCDDDCCCCCCCCCBBCBBBBBBBAAAAAA@@@AAAAAA@@@???????????????>>>>>>>>>>>>>=====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNONNNNNNNNNNNNNMNNNNNNOOOOOOOOPPQPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTUUUUUTTUUUVVUUUUUVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYZZZYYYYYYZ[[ZZZZZZZZZZ[\\[ZYZZZZZZZZZYZ\\\\\\\\\\\\[[\\]]]]^^^^^^^^^^^^^_______^^____^^^__^^______^__````````aaaaaaabbaaaaa`aaaabcccccccccccccccccccccbbbbbbbcccdddcccebWB+! #$###!"&).43.()0-**6DE@8/+-8CACIFB?CHCBDQWOA9EPRKB==>;3*,1552101000,'%%'&%$'+./-,)" '-00/0-'$#$%&$&)+)# "&'&%%$%%')*,+)*,--+(&%%%&'(((''''()'%#! ! !"'>Xionkjlmmomjlmlllllkkkjjjlllkkkjjjjjjjkjjjjjjkkkkllkjjjjjjjjjjjiiiijjjjjjjjjjjjjjjjiiiiiiiiiihhhhhiiiiiiihiiiiihgggggggffffffffghhgfgffefffggggggffffffffefggffgffffeeeefffffeeeeddeeeeedbbaaabaaaaabba`___^]\]ZY\ZTPMNU\\]V<(#(4?A:++7:9767751-++*'$!!"#'*+&$%$#$"!)+%##&(($"#!"! !!! ! #$# !'-4778<A@><>BHJHHILMMKJIJKKKKKJJJJJKKKKJJIIIIIIIIIHIIIHIIIIIIHHHHHIIIHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDEFFECCDEEDDCDDDDCBBCCCDDCBCCCCCCCCCCCCBBBBBAAAA@@@@@AAAAAA@@@@??????????????>>>>>>>>>>>>>=====<<<<<<<<============LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNOOOOONNNNNNNNNNNOOOOOOOOPOOOOPQQQQQQQQQQQQQPPQQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUUTUUUUUUUUUUVVVWWWWWXXXXXYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZYYZZZ[[ZZZZZZZZ[[[[\\ZZZZZZZZZZZZ[\\\\\\\\\\\\\[\]]]]^^^^^^__^^^^^^____``____```___`_^^_```____`aaaaaaaaaaaabbbbbaaaaaaaabbccccccccccdddcccdddddccbbbbbccddddddccfdS;& #$%%#! !$%%&+31+)-5*$.;EG<("*>FE<;B@@GKJ?@O^[@>LY[PD=CEB5+(1;:4-.4;93.,*(&&&(+/220.-)" !"'09><70-*)&##$$#%*,) "$')(((((((*+-/0/-/111/-+*)*,)()*++,,-,*'$#! "#"6Peonkjlmmonllmmlllkkkkjjjlllkkkjjjjjjjkjjjjjjjkkklkkkkjjjjjjiiiiiiiiiijjjjjjjjjjjjjiiiiiiiiiihhhhhiiiijjiiiiiiihgggghgggfffggggghhgggfffffggggggggfffffffffgggggffffeeeefffffeeeeddeeeeedcbbbbbbccccddcbaa````a^\^YPKHGOVTPE.#(/6<<8:;;>@<;==80-+),/*&&%#""#!!$!")(%*,(""%'&#" ## !"" !!! #!%%"!! $.58;?C@;558<==?CILMKJIKLKKKJJJJJJKKKKJJIIIIIIIIIIIIIHIIIIIIIHHHIIIHHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDEFFEDDDEEEDDDDDCCBCBCCCCCBBCCCCCCCCBCCCCBBBAAAA@@@AAAAAAAA@@@@@?????????????>>>>>>>>>>>>>=====<===================KKKKKKKKKKKKLMMMMMMMMMMMMMMNMMMMNNNNOOONNNNNNNNNNNOOOONOPPPOOOPPPPPPPPPPPPPPPPPPQQRRRRQQQQPQQRRSSSSSSSSSSSSSTTTTTUUUUUUUUUTTTTTUUUUUUUUVUVVVVVWWWWWWWWWWWXXXXXXXYYYYYYYYYYYYYZZZZZZZZZYYYYZZZZ[ZZZZZZZZ[Z[[[ZZYZZZZZZZZZZ[\\\\[\\\\\\\]]\\\\]^^^^^]__^^^^^^^^__``__^^__````__^_``a``__`aaa````````aaaaaaaaaaaaaaaaabbbbbbccccccccccddddddcccccddbceedcddcd_M8)!!%(($!"%" #)/3/*+.1)(:DE;-#1DNF76:AEJPI@CN]aV@EVWOC?@EA7/,16965689972,'$$'+/24541-+**(')1;EFC;50-*)-0+('),-+& ! ! !%&&%%'*-0/++-/000000001221100000000-+-../010.*('&# ! !"*>[kokjlmmonllllllllllkkjkllllklkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjiiiiiijjjiiiiiiiiiiiiijjiiiiihhhhgghhggggggggggggggffffffggfffffffffffffffggggggggffffffffffeeeeeeeeeeeeddcccccdeecbbceda``adb^_^ZVUZTD??@90-+)+/357;=<98<=<962,))))())# !" !'))(((% $%%$%&'%"""! ! !!"$# !!""#%# !!!!!#!! ",11.-/.,*)+//,4@ILIIHHFGMKFEGKLKJJJLLJKKJIIHIJJIGHIIIIIIIHIIHHIIIHIHHHHHHHHGGGFFFFGFFFFEEEEEEEFFFFFFEDEEEEEEEDEDCDDDCCCCCCBBBCCBCCCCCCCCCBBBBBBAAAAAAAA@@@@@AABAA@@?????????????>>>????????>>>>>>>==<======<<<<<<<<<<<<<KKKKKKKKKKKKLMMMMMMMMMMMMMNNMMMMNMNNOOOOOOOOOOOOONOOOONOPQPOOOPPPPPPPPPPPPPPQPPPQQRRRRQQQQPQRRRSTTTTTTSSTTTTTUUUTUVUUUUUUUUUUUUUUVUUUUUUUVVVVWWWWWWWWWWWWXXXXXXXXYYYYYYYYYYYYZZZZZZZZZYYYYZZZZ[ZZZ[[[[[ZZZZ[[ZZZZ[[[[[[[[[[[[[[\\\\\\\]]\\\\]^__^]]^_^^^^^^^^__``__^^__`a``_^^_```a````abaaaaaaaaaaaaaaaaabbbbaabbbbccccbccccccccccdddddddcccdddbcfecbdec_WG6+"!&)*'#!"!'.43/**-1/3BD=0).BQOA48@FORMFDOZa^SEIRLD>BFF=3003:?<<<;951,(%%+16:<:61-**-004:BGGD?:51..255/+*+)'&$! !"" ! "&*-...0355542211112344554222234455554335433342/+*)(&#""##!!"#0Mbjkklllmmmmmmmmmmmllkkkkllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjiiiiiiijjjjijjjjjjjjjijjjiiiiihhhhhhggggggggggggggggggggggggggggggffffffffgggggggggffffffffffeeeeeeeeeeeeeeddddddeedccdfdb`acdb^_^WTTXN8.+-*)23,*+-0368943<?<62/+*.,'&&%!"#$"!#')'&''$ $*,-+)('&$"!!""!#$$###!!#!!"! "&&$ !$#"&'$ $)*% "! "! !()'!!#$$%&&$#.>JMIEBCFJMICBEJKLKJJKJIJKKJIIIJJIHHIIIIIIIHIIIIIIIHHHHIIIHHGHGGFFFFGGFFEEEEEEEEFFFFFFFDDEEEEEEDDDCDDCCBCCDCBABDCCCCCCCCCBCBBCBBABBBBBBBAA@@@@ABBAA@@??????????????>>????????>>>>>>>==<=====<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMMNNNOOONNOOOOOOOOOOOOONOPPPPOOPPPPPPPPPPPPPQQPPPQQRRRRQQQQQRRRRSTTTTTTTTTTTTTTUUUUUUTTTUUVVVVVVVVVVVUVVVVVVVWWWWWWWWWWWWXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZYYZZZZ[[[[[[[[[ZZZ[[[[Z[[[[[[[[[\[[[[\\\\\\\\\]]\\\\]^__^^]^_^^^^^^____``__^^__```_____`_```aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccccdddcccdddddddcccddccegdbadd_WMA4)"#&)*)&" &.463.++.4:>A;2,1?ILC<<AHMUSEEQ]c`VLHFEA@CIKD<6875=EFC<731+'(*.8?@A?93-*)-499AJLG?;::8514;940/.*# %''%$""$%" "! !"""&/345668:<=;98974101258:;<833579:;;;:99:<=;865541.,+))))'%"! " (@Vekmmlkkmnnnnmmmmmlllkkllllllllkkkkklllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkjjjjiiiiiijjjjjjjjjjjjjjjjjjjiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggfgggggggfffffffffffeeeeeeeeeeeeeeddddddeddeedcbbcdcbb_XPJFD9)%%%'-77-))+.145523<?<4-*(*1/($ !$$%'%" #&&%$&'')-353-&!!"%("%)*('&'$"" "&&# #%#!'*'!&/3-!$# !! #! #$&' "-:DIF@;:DKIFB@BFHJKLKJIHIJKKJJIIIIJJJJIIIIIHHHHHHHIHGGHHHHHGGGHGFFFFGGGFEEEEEFFFFFFFFFEDEEEEEEEDDDDDDCBBCCCCBBCDDCCCCCCCCBBBBCBBBBBBBBBBAA@@@AAAAAAA@??@@???????????>???????>>>>>>>>==<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMMNNNONNNNNNNNNNOOOOOONNOOPPPOOPPPPPPPPPPPPQQQPQQQQRRRRRRRRRRRSSSTTTTSSSTTTTTTTUUUVUUTUTUUVUUUUUUVVVVUUVVVWWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZ[[[[[[[[[ZZ[[[[[[[Z[[[[[[[[[[[[\\\\\\\\\]]\\\]]^^^^^^__^^^^^^____``______`````````````aabaaaaaaaaaaaaaaaaaaaaaaabbbbbcccccddddddddcccddddddddcccbbdffbaceaWK@90& #')*)(&$!$,464/,-/49AB91.3=GD<69CJOOPMEJXglaL>CB>?DKOL@::BECBDHC92/.*).5=ACA<61-+,05<ABGJHB=;:;:899863343+##(+($#" !#(*)% "%((%! !!!"%).37;=@A@???@?><;:8778:<=>>>;9:<>@ABA@?>=?@?=9876541//--//,'#""! ""6Ndmpmllmnnnmmmmmmmlllkllmmllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggfffffffffffffffeeeeeeeeeeeeedddeedccccbbbcefcdf_QB5.+'!',(*074($%*.399546==80)'&(--("!""$')'""&&$!!$&%&,23-%#!$)($ #(+,)&%'#"$""%%# #&% $/51'"($ ! ""#$%+15;>:517?CB?=>ADHLNMJIHIJKLLKJIIJKKJJJIIIIHHHHHHHHGGGHHHHGGGHHGGGFFGGGFEEFFFFFFFFFFFFFEEEEEEEEDDDDDDCBBCCCCBBCDDCCCCCBCBBBBBBBBBBBBBBBBA@@@AAAAAAA@@??@@@??????????????????>>>=>>====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMNNNNOONNMNNNOOOOOOOPONOOOOPOOOPPPPPPPPPPQQQQQQQQRRRRRRRRRRSRRSSSTTSSSSSTTTTTTUTUUVUUTUTUUUUVVVVVVVVVVVVVVWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZYZZ[ZZZZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\]]]]]]]]]]]]^^^^^^^^^^________``_____`````````````aaabaaaaaaaaaaaaaaaaaaaabbbbbbbbccccccddcddddccddddddddddcbaadfeaaee\J=51)!!$*..+'$"""!")1773,+/6=AHE5+,6=<4/5>HMQTPJGN[kkYG?IF?GORNG>=AHKJECG@50./,-5=CB>:3-**-29?DHJIFDCB@><<?A?98751-(&*..)$!""!"$'('$"!(..-*&! !! !#$'*/6=@>@CEFECAAABB?::<>@BCCBBA?@ABDEFFFEEDCCBA?=:988875320022.)%#$#"!/IbmplnoonnnnnnmmlllllmmmmlllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjkkjjjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhgggggghhggfggggfffffffffffffffffeeeeeeeeeeeeeddfgcbcccbcdddcadibTE7-'" $)*,.1.&&(**+/2136850+**((*($ $''!!#$# !#$# #)0,%##!!%'%"#'+,+&" ! !!!"%&%!$040& #%$! "" #%%&+242.27874335:@GKKIHIIIJKLKJIIJKKJJJIIIIHHHGHHHHGGGHGGGGGGGGGGGFFGGGFFFFFFFFFFFFFFFFEEEEEEEEDDDDDCCBBCCCCBCCEDDCCCCBBBBBCCCBBBBBBBBBBAA@@AAAAAA@@@??@@@@?????????????????>>>=======<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMLMMMMMMMMMMMNNMMMMNNNNOONNNNNNONOPOOOOOOOPOOOOOPPPPPPPPPPQQQQQQQQQRRRRRRRRRSSSSSTTSSSSSSSTTTTTUTUUUUUUTTTUUUUVVVVVWVVVVVVVVWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZ[[[[[[[[[[[\\\\[[[\\\\\\\\\\\\\\\]\]]]]]]]]]^]^^^^^^^^^^_______________````````a`````abaaaaaaaaaaaaaaaaabbbbbbbbbbcccccccccccccccccddddddddddddcaadedbdhdT;.*)$!%*132,%! !$&).4;;7/.3=EJNG4+,44.)-<FLNQTPJFQakbKGJOJEPURIA?AEGGECBC;201335<AB=740-,/4;BHLOQNJJJID?=?CEA9983,(*1881%!"###"#$%%&(*/2/,'"!"""$$"!$)/5:?CHIEEGGFEDEEFFFC>?CFHIIHFEDCEHIJKKJJIIHIIEBA?>=<<;:9753231.*'&%$# '@Zhnlnponmnnnnmmmmmmmmnmmmmmmmlllllllllllllllllllllllllllllllkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjkkkjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhggggggggggfggggfeeffefggggfffffffffffffeeeeeeeegfcbdeedeecbbace]SMC91)$"#&''('&)--*'),.132.*-/0+(($ ! "$$! !####$$" $+)%""! "#$$'*-,(# "()&$/62) (($" ! "(,--46.**(()/7AGJIIJIHIIKKKJIJKKJJJIIIIHHHHHHIHHHHHHHHHGGHHGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEDDDDDCCBBCCCCCCDEEDCCCCBBBBBCCCBBBBBBBBBBAA@@AAAAAA@@@??@@@@@????????????????>>>==========<================LLLLLLLLLLLLLLLLLMMMMMMNNNNNMMMMMNNNOONNNNNNOOOOOOOOOOOOOOOOPPPOPPPPPPQQQQQQQRRRRRRRRRSSSSTTTTTSSSSSSSSTTTUUUUUUUUUUUUUUUUUVVVVVWWVVVVVVWWWXXWWWWWWXXXXXXXYYYYYYYYYZZZZZZZZZZZZZZ[Z[ZZZZZZZZ[Z[[[ZZZZ[[\[[[[[[[[\\\\\\\\\\\\\\]]]]]]]]]]]^^^^^^^^^^^^_____________``````````a`a```aaaaaa`aaaaaaaaaaaaabbbbbbbbbbcccccccccccccbbbddddddddddeeedcceeddgj`I-!"#!%+1562+%""$(-138?A>748BJNOG6///+&*6FMMNOONLGUhhWAITOLLRSNE?BDD?<;>@=7136:<?CB=744568;@EKPSUVUTSPIC>?ACC>9872--5=A;/#!%'(&%&'')+/20+)&"!$&''('&'+4=FKNNMLLKIGFEGJLMLJIHIJKLLLKKKJJKLMNNNNMLLLMLIFEDCCBA?=;96542/,+*)'%#! !! !!!! 2L`kmnnmmnnnnnmnnnnnnnnnmmnmmmmmmmmllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkkjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihgggggggggffgggggfffffffffgggggggfffffffffffffeeeeefebcfffffebbcdaZQMRPH?5-%! !##$'+.0.+,,,.0.,,364-'(!!!! ! #""#"!!#&'&$""$%&%#! "# "%(++($ !"""!! "$('$ .74-&$(.)!& !!!!!#&+56($%"!"'0:CGHIJIGGHIKKJJIJJJJJIIIIIHHGHIIIHHHIIIIIHHHGGGGGGGHHGFFGGGFFFFFFFFEEEEEEEDDDDDDDDDCBBCCCCCCDEEDCCCCBBBBBCCCBBABBBBBBBAAAAAAAAAA@@@@@@@@@@@???????????????>>===========<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMMNNNNNNMMMMNNNNOONNNNNOOOOOOPOOOOOOOOPPPPPOPPPPPQQQQQQQQRRRRRRRRSSSSSTTTTSSSSTTTSTTTTUUTTUUUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWXXXXXXXXYYYYYYYYZZZZZZZZZZZZZZ[[[[[ZZZZZZZZZ[[[Z[Z[[[[[[[[\\\\\\\\\]]]]]]]]]]]]]]^^^]]^^^^^]]]^^^^^___``````____`````````aaaaa``aabaaa`aaaaaaaaaaaaabbbbbbbcccccccccccbbbbbbbcddddddddeeeeffeefedfhfW>$ #-35761+'()+.26<ADC@;:>FKKF?866/**5CMPNLKKKNS^fZOIMOHIMNMKGDB@>:9=><8659>BFHGB;6469>DJOTXYYXXWUOG@?@AA?>;88:::<@A<1)&'*-/01110.,+,($#"! "%(+-.-.16<CJORSRPNONMKKKLNPRRQRTSQPOOPQRRQPPQQRRRQPONMLLJIHGGGFDB?>;9:951..,+(%#!!"##$$%%%$#"!! !! %=Ugnomkmponnnnnnnnnnnnnmmmmmmmmmlmllllllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkkjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihgggggggggffgggggffgffgffgggggfgggffffffgffffffffffedbdfeddddbehe\PIKWYQH=4/+')-+)'%&+,,.-+,--/3993+&(#"! !#%$##"#'%!!&&# !%% "!#%&'((&$! !"! !"!#%('$""%(*24-*+..*#!$#"!$""&,-(''%"!%,4;ADFGGFFHIKKJIIIJJJJIIIIHHHGHIIIHIIIHIIIHHHGGGGGGHHGGGGGGGGFFFFFFFEEEEEEEDDDDDDDDDDCBCCDCCCDEDCCCCCBBBBBCCCBBBBBBBBBBAAAAAAAAAA@@@@@@@@@@@@????????@@????>====<========<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMNNNNNNNMMMMNNNNONNNNNONNNNOOOOOOOOOONOPQPPOPPPPQQQQQQQQRRSRRRRRRSSSSTTTTTSSSSTTTTTTTTTTUUUUUUUUUUUUUUUVWVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYYZZZZZZZZZZZZZZ[[Z[Z[[ZYZZZZ[[[[[Z[[[\[[[[[[\\\\]]]]]]]]]]]]]]]]]]^^^^]]^^^^^]]]^^^^^___``````____``a`````aaaaaaaaaaaaa`aaaaaaaaaaaaaabbbbcccccccccccccccccccbbcddddddddeeeffefffedfd]H0 *69873.**.0247=EIGC>;?EJIC<8;;6-*1@NTTOHIQOP[_[MKTQMKKKIJLLIC?=<?EA<;<?BEHLKE>:99;@GOW\ab`]YURMD><@DEC@@ABDFGEA<6/*+2447998653/)%#%'&'()+-.147:;>DINQRSTUVVURRSTTTSSUVXZ\][YWVVXYYYXWWXXXXXWUTRPNMMMLKLKIFDCB?=?>:521.,)'%%%&'(()))('%$#"""" .G^lonmoqonnnnooonnnnnnnnnnmmnnmmmmlllllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhggggghhgggggggggggghgggggggggggggffffffffffedcdebabca[]`YRNPWcd[OA5.+*,/.+)#!'*,.-(')-26751-*)%!!!"! "&(%#! !'% !&%" #! #" #(//)$ "!!#$"#(*)"!%-232/,.12,#"" %$ " %%$#&('$! $)/49>BCEFHJJKJJJJJJJJIIIIHHHHHIIIHHHHHIIHHHHGGGGGGHHGGGGGGGGGFFFFFEEEEEEEEDDDDDDEEEDCCCDDCBCDEDCCCCCCCCCCCBCBBBBBBBBBBAAAAAAAAA@@@@@@@@@@@@@@?@????@@@@???>>===<<========<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMNNNNNNNMMMMNNNNNOOOOOOOOOONNNNNOOPOONNPPPPOPPPQQQQQQQQQRSSSRRRRRSSTTTTTTSSSSSTTTUTTTUTTUUUUUVVVVVVVVUUVVVVVVVVVVVVVVWWWWWWXXXXXXXXXXXYYYYYYZZZZZZZZZZZZZ[[ZZZZ[[[ZZZZ[Z[[\[[Z[[[[[[[[[\\\\]]]]]]]]]]]]]]]]]]^^^^^]]^^_^^]]]^^_^^___``````____``aa````aaaaabbbaa```aabbbbbbbbbbbbbbbbccccccccccccccccccccbbcddddddddeeffedeeecce_Q9$$1==961+),268:>CILIB<<DLLF<89=7/*/;HRUSOKOWROVVQMOVQMROHGJOOKFBBCFJC=@FKJKMOK@;;>@CJS[acedb_YPJE@?BFIHHGJKLNOQJ=1**.5>BACB=731//-,,.000369:<>@DHLQUXZXWVWZ[\[XXZ\]]\\\]_acba`_^^__`_^]^_^^]\\ZYWUSQRSRQQPMIGFFECB@=963/,*)*+,,,,,++*((&%$$$$#! ! #8Qenppponnnnooooonnnnnnnnnnmmmmmnmmmmmmmmmmmmmmmmmmmmmmmmmmlkkkkkkkkkkkkkkkklllllkkkkjjjjjjjjjjjjjkkjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhgffffgggggggggggfffffffffgffffffffffeeebbec[MJLIKQY`ebYPB3+*/1/,*+%!&),/-(%'.35/+-0/&$%""#""$'($! !'&"!$'%"! !"#" "$$)52( #!!##"$)*)#$*1550.255/&!$" ! ## "&% "$$" $*07<?CGJKJJJJKKJIIIIIIIIIIIIIIIHHHHHHHHGGHGGGGGGHHHHGGHGGGGGFFFEEEEFFEEEDDDDDDEEEDCCDDDCBCCDDCCCCCCCCBBBBBBBABBBBBBBAAAAAAAA@@@@@@@@@@@@@@@@@@??@@@@????>==<<<<========<<=============MMMMMMMMMMMMMLLLLLMMMNNNNNNNMMMMNNNNOOOOOOOOOOOOOOONOOPPONOPPPPOPQQQQQQQQQQRRSSSRRRRRSSTTTTTTSSSSSTTUUTTUUUUUUVVUVVVVVVVVVUUVVUVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYZZZZZZZZZZZZZ[[[[[[[[[[ZZZ[[[[\[[[[[[[[[[[[\]]]]]]]]]]]]^^]]]]]]^^^^^^]]^^__^]]]^^_^^___``````____``aa````aaaaabbbaa```aabbbbbbbbbbbbbcccccccccccdddddddddccccccddddddddeeffecdddcacYF- !)5@?96/**.58;=CIHHHB>AIPLC;?A:-$*7ELNMLMVZTOICGQZVJKQUOGHLPNKJKMIGF@>CLSONOPJ=;@DHNXaffdb`__YLFFIMPOLLNQUSMNQQF6*)4=DFJMLF<2+*.4<CGB;::<@DGHJLOSZ^_^][[\_bb`_````aceffffeeeeeeddccbccddcbaa`_^]\ZYXZ[XWWURMJIIIGB>>;84/-,-/2310/.,+)(''&&&&%$"!!"#$# -E]kqrokmonooooooonnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllkkkklllllkkkkkkkkkkkkkkkkkkjjjjjiiiiihiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhgggggghhhhhhhhhgggggggggggggffffffghhhgegjeV@33=IRXYTNJKC4+-:90*),(%(*,00,)*240%"*1/ $!!##$&'&# !$*)&&')&#! !&('#!!$')')3/##'%!#%""##%(*)(')-00-.3;;4(!!"#"##!""!!$""#! ! %.47<CHKKKJJKKKJIIIIIIIHIIIIIHHHHHHHHHHGGGGGGGGGHHHHGGHHHGGGFFFFEEEFFEEEDDDDDDEEEEDCDDDDCBCDCCCDCCDDCCCBBBBBBBBBBBBBAAAAAAA@@@@@@@@A@@@@@@@@@@??@@@@????>>==<<<========<==============MMMMMMMMMMMMMLLLLMMMNNNNNNNNMMMMMMNNONOONNNNNNNNOPPOOOPPPOOOPPQPPQQQQQQRRRRRSRRRRRRRSSTTTTTTTSSTTTTUTTUUUUUUUUUUUUUUVVVVVVVVVVVVVVVVVVVWWWWWWWXXYYYYYYYYZYYYYYYYZZZZZZZZZZZZZ[[[[[[ZZZ[[[[[Z[[\\[[[[[[[[[\\\\]\\]]]^^^^^_^^^^^^^______^^^____^^^^_______```````````aaa````aaaaaabbaaabbbbaaabcccbbbbbccccccccddddddcccccccbbbbbccdeeeeeddeefedddddbZI3""/;BC@:-#+38:<AFHIFA?AJSUC:>ED7+$,8ENQNMPWXRJDDP^[QFLRPJFJPUTQOPRPI@AEJOQNLKIFCHNPU]flljgfd`ZTPQTTQOSZbbZMIPVVK=1.3<FLNMIB;77;BINMKIDABDGKOSVX[\^bdcbabceeedeedegijjjjllkjihhhijjiihhhgfdefffdb`_^^]]][ZYWTQNKIGDA?@>;732356752/.-,+,,+)'%$%$$$%&%$#!"5Pdnonlnommmopqpomoonnnnnnnnnnmmmmmmmmmmmmmmmmmmmmnnmmmmmmlkklllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjiiiihiiiiiiiiiiihiiiiiiiiiiihiiihhggggghggggggghgggggggggggggggffffggcegbbfg]G/"+8CHJJHGSP>413562.---,*()*+.021-%&/-'!!"##$%$##$&+/.,) "$$$%&&&$#$'**'%&+13)"!!"! !#$$ !!",0,#&.352,,076.(%&&%#"!$!#&%! "% !! ""!',158>HIFIKKJIKKJJJJJJIIHIHHIIHHHHHHHHHHGHHHGHGGHHHHGGGHHGGGFFFFEEEFFEDDDEEEEDDDEDDDDCDCCCCDCCCCCCCCCCCCCCBBBBBBBBBBAAAAAAAAA@@@@@@AAAA@@@@????????????>>>=<==============<===========MMMMMMMMMMMMMLLLMMMMNNNNNNNNMMMMMMNNNNOOONNNNNOOOPPOOOPPPPOOOPQQQQQQQQRRRRRSSRRRRRSSSTTTTTTTTTTTTUUUUUUVUUUUUUUUUUUVVVVVVVVVVVWWVVVVVWWWWWWWWWXXXYYYZZZZZYYYYYYYZZZZZZZ[[[[[[ZZZZ[ZZZZ[[\[ZZ[[\\\[[[[[[[\\\\\\\]]]]^^^^____________________________```````aaaaa```aaa`a````abbbaaaaabccbcbaabccccbbbbccccccccddddddcbbbbbbcccccccdeeffedddeeddfgd^UH6&%4?BEB8*#2=>=>CHIIE?BIOSO=:FJE6)&0>KSUQRUXTIDGOY^UJEHKGDEMUWWVVURKC?FOPPOMJIFGKRZ_cglooligc^WTWYYWW\fnnfXIHSXSD838@FKNKIGBCHNSVWVPIEHLMPTW[^adfghhhgffgijihgijikmoppoopponmmmmmmnnnmmlkjijjjigecbbbcb`][XURPNLIGFEDC@=:99:::86531/--..-+(&%&%%&''%#"!)AWgmpopommnoppponopooonnnnnnnnnnmmmmmmmmmmmmmmmnnnnnnnmmlllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjkkkkjjjjkkjjjiiiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhggggghhhhhgggggghggggggggggggggggebcfddgh`M:-(+38?IKLXXKB923763/00/*&&(+/10.,'*0*#"! #$#!!!%)-0/.+''' ".576662-)%$),,'$%-1)%'$!! $&%#!" &.0*!)0342-/240)&%&%$$" %#%)& " "# " !!!'+,+/9HKFHJJIIKKJJJJJJIIIIHHIIIHIIIIIIIHHHHHHHHHHHHHGGHHGGGGFFFFEEEFFEEDDEEEDDCCDDDDDCCCCCCCCCCCCCCCCCCDCCBCCCBBBBBBBBBBBBBAAA@@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMMMMMNNNNMMNNNNNNNNNMNNNNNOOONOOOOOOOOOPPPPOOPPQQQQQQQQRRRRRSSRRRRRSSSTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUVVVVVVVVVVVWWVVVVWWWWWWWWWWXXXXYYYZZZZYYYYYYYZZZZZZZ[[[[[[[[[[[[ZZ[[[[[[[[[\\\[[\\\[\\\\\\\]]]]]^^^^^_______________```````````````````aaaaa```aaaa``aaaabbbaaaaabbbbccabccccccbbcccccccccddddddcccccccccccccdeeeeeddccddcchi`Q@2'!!,8@AD@1')<FD@>BHKGDCLSQKC;?MLB4)(6GSXXVWXWMABMY[TLGHEBBCHTZYXW[VLA>CNUSPNLJJKNTZakollmnkfb^\[\`]YZes}zl\PNRXUMB;;AHLNMJKPRX`ba[VRMJJS\\^adfhjlmnolllllkklllkmoppprsstttsrqrssrqqqqqqqpppponmkhgfffghfc`\XTQONMMLLLIEB????>=;989863100/.,+))))(('&#"""!"1E[krqpooopoonoopppoooonnnnnnnnnnnnnnnnnnnnnnnmnnnmmmmnmmmmmllllllllllllllllllllllllllllllllllllkkkjjjjjjkkkkjjjkkkjjjiiiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhhggghhihhhhgggggggggfffffffffghgfedefhiie[KGF8.-/8HNOSTOG=6225520/1-()+,--.-+*,/+%#####$##$+3872( "()0=DFD@;4/.+)))('(*10#$)('$"#'&"!!!#'**(%$'.0...,261+&%$"!"$#!#"#()%"&(&#"#%&" "# !!!!!!%)' &6HMJIHHHIJJHHIIIIJIJIIIIIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGFFFFFEEFEEEEDEEDDDCDDDDDDCCCCCCCCCCCCCCCCCCCCCBBCBBABBBBBBBBBBAAAA@@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMNNNNNNNNNNNNMMNNNMNNNNNONOOOOOOOOOOOOOPPPPPPPPQQQQQQQQRRRRSSSRRRSSSSSTTTTTTTTTTTTUUUUUUUUUUUUUUUUUUVVVVVVVVVVWWWVVVWWWWWWWWWWXXXXXXXXYYYZYYYYYYZZZZZZZZ[[[[[[[[[[[ZZZZ[\[[[[[[\\\\\\\\\\\]]]]]]]]]]^^^^^^^^^^^^^_______``````````````````aaaaaa```aaaaa``aaabbbaaaaabbbbccbccdddcccccdcccccdddddddddddddddddddddddeeedcccccddcheVA."&4>CBB;+'.AIE@=@FJDCJW]RF<;BLD7/)->MVZ\\[WNFBFNXTKHIMHCGLT_db]XUMC>ALRTOKJKMOSY_diqtrqokd^\^bfjmiemz†‚s`USV[\VMFBACGKNRXaigdc^XTSVZ^chklmopqqrrrrsrsutsrqrqrstuttttuwxxxxxwxzyxwvvvvvvvvvvusrpnlkkkllida]YVTTTTTTSQMHEDEEEB@=<<>=;854210/..-,++*(%"!"#" 0Hbnoppppqpnmnoponooooonnnnnnnnnnnnnnnnnnnnnnnnnnmmnnnmmmlmmllllllllllllllllllllllllllllllllllkkkkjjjjjjjkkjjjjkkkkjjjjiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhhhhhhiihhhhhggggggggfffgggggggggffhhfhhd^SDGPOE6++9DHEB@9351-240..10-/0.,+-,+./.+'$" $.57652,%$.653331/*$"#&)++)(*/5<<30/.)$! "''$ "" $'*+'$#',2/)***02*%"%$ !%&$! !&)($ "%&%%(*(!!$$" """! ! #%#$3ELKIIIJIHGGGHHIJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGFFFFFFFEEEEEEDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBABBBBBBBBBBAAAAA@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMNNNNNNNNNNNNNNNONNNNMMNNNOOPPPPPOOOOOOPPPPPPPQQRQQQQQRRRRRSSSRRRSSSSTTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXYYYZYYYYYZZZZZZZZZZZZZZZZZZZZZZZZ[[[[[\\\\]]]]\\\\\]]]^^]]]]]]^^^^^^^^^^^^^^______`````````````````aaaaa``````aaaaaaaaabbbbaaabbbcccccccdddcccccddcccdddddddddddddddddddddddddddddddddddecYF2$"-;ADD?6++4AEA?>?CF@DR`bRFAEILC6+*2@KOV\_\TFBJLMMKIJLLJLT^ehig`UKDCFLPRPJHINSX^ekorwxsojfb`cglnopsx…Œ‹{h\Y\^]ZOFEGKPV\`elnkhfebabfjklmrwwxzzyxwvuuvxz||{yxxyzz{zzzzzzzz{|}~~€~|{zz{{{{zzzzxwvtsrpppojedb`___^][YWRNLJJKJIEBA@@@?=;865332220--,+)%"!#$$!5Ugmoopppqomnopnmnooooooonnnnnnnnnnnnnnnnnnonnnnmmmmnnnnmmmmmmmllllllllllllllllllllllllllllllkkkkkjjjjkkkkjjjkkkkkkjjjiiiiiiiiiiiiiiiiiijjjjjjiiiiiiiihhhiiijihhhhhhhhgggghhhggggffffffcchfcda[VNACO]YC4.5>C<63,),)%),*)*-,+.0.++.-+13/-*$! *6=;6.(#!%+.253-'$#%!$(+/21.-08@GG?;972-,+' "%#!$%# "&)*'%$%(-1,&'((+*#!!%%! %'&! $()&$! !"$'+*$##" !!!$$"!" !!#'1BJKHFGHGEEFGHIIJJJJIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGFFFFFFFEEFFEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBAAABBBAAAAA@@@AAAA@@@@?????????????>=============================MMMMMMMMMMMMMNNNNNNNNNNNNNNNOOOOONNNMMMNNOOPPPPPPONNOOPPPPPPPQRRQQQQRRRRRRSSSSSSSSSTTTTTTTTTTTTTTUUUTTUVVVVVVVVVVVVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXXYYYZYYYYZZZZZZZZZZ[[[[[[[[[[[ZZZZ[[[[[[\\]]]]]]]]]]]]]^^]]]]]]^^^^^^^^^^^^^^^_____````````````````aaaa`aaaaaaaaaaaabbbbbbbbbbbbbbccccccddddcccccddccdddddddddddddddddddddddddddddddddddee]L7' (4?@CE<2-1:>?=>@?AD@GXc`NGHMNKD8+*5@HJS[]YQDCMPMGGJLIFKVblpkfd]SGDIQSPQPKJNU\bipuxxzwpkhhikortttwˆˆzi]Z[^]ZXMHMV_fmqqqtpihjlmptx{yvty~€€~|{{{{~‚‚€~~‚~}~€ƒ„„„„ƒ‚€~~}~~}|{{zywutrqliiiiiihfb^[WROPOONMKHFDDCA@?=;97554342.--,*&""$&%#! %D]jnoonnqqoooqonopooooooonoonnnnnnnnnnnnnooonnnnmmnnonnmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllkkkkkjjkkkkkkjkkkkkkkjjjiiiiiiiiiiiijiiiijjjjjjjiiiiiiiiihiiiijjihhhhhhhhhhhhiihhhhgggfffcbhgcc]SQMCCKZWE<63461032-'$$%&()*)('+-.+,0-*35/.+#!&/7971-*'&'.1,*)+(# !&''/33699758>DGGA<742221-&"%$ &((%&()'$'(()*-)#%(+($ !##! !$&'#$**)$" !#%'))'#""" !#$$#! ! %'-;EJGFGHHHHHHIJJKKKKJIIIIIHHHHHHIIHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEFFFEEDCCCDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBAAAAA@@AAAAA@@@?????????????>>===>>>>>>>>>>>>=============NNNNNNNNNNNNNNNNNNNNNNNNNNOONNNNONNNMMMNNOOPPPPPPOONOOPPPPPPPQRRQQQRRRRRSSSSSSSSSSTTTTTTTTTTTTTTTUUUTUVVVVVVVVVVVVVVVVVVVWWWWWWWWWWWWXXXXXXXXYYYXXXXXYYYZZZZZZZZZZZZ[[[[[[[[[[[[[[ZZZZ[[[[[[\\]]]]]]]]]]]^^^^]]]]]]^^^^^^^^^^^^^_______`````````````aaaaaaaaaabaaaaaaaabbbbbbbbbbbbbbbcccccdddddcccccdddddddddddeeeeeeeeeeeeeeeeeddddeeeeeeeedVB-!!! ##!".9@?@D9//6=;:;?A?ACAJZa[KFIMLGC;-+4AJMTYWTNHGKOOJIKGCAN^jpne^ZVQLLQUTOQRPRX_glrw{{yxsmkmswyyzz{}ƒŠ‹o^XXZYXXZ\XXcnw|~~xqnmmosxz{}€‚€€‚ƒ…††…„‚‚‚‚ƒ……………„„…‡‡…‚€‚‡‰‡„…‡ˆ‡††……„ƒƒƒ‚‚€€€€~}}|zyvuspmnooonkhc^YVSRSSROMLKJGECAA@>=;9754342.---*'$$&''$"!6Qfnpommprrpprqppppoooooooooonnnnnnnnnnnnoooonnnnnnnnnnmmmnnmmnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllkkkkkkkkkkllkkkkkkkkkjjjjjjjjjjjjjjjjiiijjjjjjjiiiiiiiiiiiiiijjiiiihhhhhhhhhhhhhhhhghhggfgjiee]OMLFEHIE=A@4*$%.:A<+(+'&*..(&')+,,-2.*24..+!$4AC7("&.0241+#%*(')1449:9:=>=<;;<;:;4+'(++**('*&"'*+*)(&$%*,+(')%!%+0*"! !#$$%&&)/.)"#&'+040) !$"!!!""#!!&*'$! !""! #"'3?HHHIKMOMIJJJKKKKKKJIIIIHHHHHHHHHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEFFFEDDCCDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBAAAAAAAAAAA@@@@?????????????>>>=>>>>>>>>>>>>>=============NNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOONNMMNNNOOPQQQPPOOOOPPPPPPPPQRRQQQRRRRRSSSSSSSSSSTTTTTTTTTTTTSTTUUUTTUVVVVVVVVVVVVVVVVVWWWWWWWWXXXXXXXXXXYYYYYYYYYYYYYYYYZZZZZZZZZ[[[[[[[[[[[[[[[[Z[[[[[[[[\\]]]]]]]]]]]]^^^]]]]]]^^^^^^^^^^_____`___``````````````a``````aaaabbbaaaabbbbbbbbbbbbbbbbccccddddddccbccddddddddddeeeeeeeeeeeeeeeeeeeeeeeeffeeed_Q=* !#$#! &2<A>>@4+.5;99<?@>?ABJX]XLFGHFB>8/-5GV]^YQLJGHJPRPJD@BIWemjb^XSPOQQPOQSQPT[dkruy{{yvpmosx}€„‡‰Š‹‡{k[TW]_`agklmr|‚ƒ~yvronrxz~€€‚‚…††‡ˆ‰‰‰ˆ‡‡‡‡ˆˆˆ‡‡‡ˆ‰‰ŠŠˆ…|†‰‰†……‰‹‹‰ˆˆˆ‡†††††„„ƒ‚ƒ„ƒ‚€}}{yxwsqqqpomjgc^[YYXXVTPOOOMJEBCCBA?=:864331/.--+)'&()(%#")B[kttqoprrpopqqpppppppoooooonnnnnnnnnnnnoooooonnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllkkkkkkkkkklkkkkkkkkkkjjjjjjjjjjjjjjjjiijjjjjjjjjjijjiiiiiiiiiiiiiiiiihhhhhhhgghhhhghhhhhhhfdbbWE@?==?ACGQR@0%"%,/1146-)-11**.21+,/3/*02,.,!#.:A?1%%,43463.+%!&'$&-9>=;99;==94/,,+-4.$"#&%""$&&$ !&)+*)'#%+,+*'&'#&/5,!! %(((()-1.'#(*,2780%!&&"!"$')'##(*(&$###$#!"(%$+8DIKMLKJJKKKKKKKKKJJJJIIHIIIIIHHHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCDDDDCCCCCCCCCBBBBABBAAAAAAAAAA@@@@????????@????>>>=>>>>>>>>>>>>>>============NNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOOOONNNNOOOOPPQQPPPOOPPQQQQPPPQRRQQRRRRRRSSSSSTTTTTTTTTTTTTTTTTTTTUTTTTVVVVVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[[[[[[[[[[[[[\\\\[[\\]]]]]]]]]]]]^^^^^^^^^^]^^^]^^^______``````````````````aaaaaaaaaaabbbaaaabbcbbbbbbbbbbbbccccddddddccbbccdddddddddeeeeeeeeeeeeeeeeeeeeeeeefffedd`XI7'#&$" !$+5<@?<8/),3977;=>?>=?IWYUMHGGGD?944=ScgbWKEEDGLQSQI@@JXbgh`WVTOMNQPNMQXRPZentxz{{yvtqrv{€‚„‡Œ‘Ž†zmd\Z_eklotz}|‚††ƒ}yxy|}‚„††…††‡…†‰Š‹ŒŒŒŒŒŒŒŒŒŒŠŠ‹ŽŽŽŒŠ…~…ˆŠ‰‰ŠŒ‹‹ŠŠŠ‰‰‰‰ˆ‡‡……†…„ƒ€~}|{zwtsrqolifda_^_^\YVSQRRPLHEFFECA?=:7543210.-,+))**)&%#/F_pusppqronooppppppppopoooooooooooooooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllmllkkkkkkkkkklkkkkkkkkkkjjjjjjjjjjjjjjjjiijjjjjjjjjiijjjiiihhiiiiiiiiiiiiiihhhhhhgggggghgggiheca_SA:89:934=ED6-(&&(,0376-(*-.)+053-.141-00+--%-;;84,'*/2..2/,.)#&'%)1::5345653/*'&),160'%%&" #&$#""! "'**)(&"(0,('&%$! (/2(!'++**,/1+" %)*-1661)##&(&$#$&*-.*%#$'**'&&')('# $! +8?FKLKKKKKKKKKKKKJJJJIIHIIIIIIIHHHHHHHHHHHHHGGFFFFFFFFFFFFFEEEEEEEEDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCDDDDCCCCCCCCCCCCBBBBBAAAAAAAAA@@@@???????@@@????>>=>>>>>>>>>>>>>=============NNNNNNNNNNNNNMMMNNNNOOOOOONNNNNNNNNOOONOOOOPPPPPPPPOPPQQQQPPPQRRQQRRRRRSSSSSSTTTTTTTTTTTTTTTTTTTTUUUTTVVVVVVVVVVVVVVVVWWWWWWWWWWXXXXXXXXYYYYYYYZZZZZZZYYYYZZZZZZZ[ZZZZZZZZZZZZZZZZZZ[[\\\\[[\\]]]]]]]]]]]]^^^^^^^^^]]]]]]^______````aa```````````````````aaaaaabbbaaaabcccbbbbbbbbbbcccccddddddcbbbccddddddddeeeeeeeeeeeeeeeeeeeeeeeefffedbc]QA0#$&" !#)05;?B;/++,39768:>B>9>JXXQKGGHIH@:9>K^if]PFBCBGNRROGBIWdgc^WQONMMMNORUWYTVcnvxyyyxwvvx|…ˆˆ‹Ž”•‡xh__dnx|}}€„‡ˆˆŠŠ†„ƒ‚‚„ˆŒŒŠ‡‡‡‰‰ŠŒŽŽ‹‹Ž‘‘‘‘‘‘ŽŽ’“’‘‘‹†…‡‰‹ŽŒ‹‹‹‹ŠŠ‰ˆ‡‡†…ƒƒ€~}|zxwvtqnjhfddcca_[XWUTSQOMKJHGECA?<:865542/.-,++,,*'&% 1Meqspoqspnoooppppppppppoooooooooooooooooooooooooonnnonnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmllllkkkkkkkllkkkkkklkkkjjjjjjjjjjjjjjjjiijjjjjjjjjiijjjjihhhhhhhhiiiiiiiiiihhhiiihggfffeeeffcb`ZNA96751'%,-+'),+**.122/)&&'('*.230245312/*./+5A920-+,--'&**+-&&+*,040)+/0/,)(()*-1451,)'%!#')$"#"! $)+*&&%"*2)##$$! #,.( !##"'++**/20''+,*0781'"#*-,%#(),01/*'(*+'()(()))% (,4<AEJKKKKKJJJJJJJJJIIHHIIIIIHHHHHHGHHHHHHHGFFFFFFFFGGGGGGFEEEDDEEDDDCDDDDDDCCCCCCCDDDDDDDDDDDDCCCCCCCBBBBBBBCCCCBBBAAAAAAAAAA@@@@??????@@@@@???>>=>>>>>>>>>>>>>>>>>>>>>>>>>>NNNNNNNNNNNNMMMMNNNNOOOOOOOONNNNNNNOOOOOOOOPPPPPPPPOOPQQQQPPPQRRQQRRRRRSSSSSTTTTTTTTTTTTTTTTTTTTTUUUTTUVVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYZZZZZ[[ZZZZYYZZZZZZZ[[[[[[Z[[[[[[[[[ZZZ[[\\\\[[\\]]]]]]]\]]]]^^^^^^^^^]]]]]]^_____```aaaaaaaaaaaaaaaa`aaaaa`aaaaaabbbaaaabccccbbbbbbbbbbcccdddddddcbbbccddddddddeeeeeeeeeeeeeeeeeeefeeeefffdcab[L:)#%!%-359?C;)*/16<866:@G@7@P]XMDBDFHH?9=HWdeYPHDCDFHMPPKHJVagbZTRQIGKMNLS]c`UWbnwzxvuuvwy|‚‡ŠŽ’”•”’„woot}†Œ‹‹ŠŠ‹‹Ž‘’’““ŽŒ‹Š‹ŒŒŽ‘““‘’•”’“““””•–——˜—•“‘’”––”““”“’‘ŒŽ‘““’‘‘’’ŽŽŒŒŠˆ‡‡††…ƒ‚€~}||{zxuqnkhgffec`]\[YVSQQSQLIGFDB?=<:988730.--,,,,+('%!! ";Wjpomptrpqqqqqppppppppppppppppppppppppoooppppponnnoonnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmnmlllkkkkkkkllkkkkkklkkkjjjjkkkkkkkkkkkkjjjjjjjjjjjjjjjjiihhhhhhhijjiiiiiiiiiiijjjhgfeedcbbcca_[RF=621+%&'&! ',-*)+**,+('''&&')++/36765440+/226:7793,''($!"+0+ &//+),+)*,-+)(,021-)'**(%" !&*+%!"! %+-*%$$$+0%"#" &0-(,*$%()((36/#%*++,5>;0#"&-.+&'-/142-('+-+"'/+%"!!#-7AIKKJJJIIJJJJKJIIHIIIIIIIHHHHHHHHHHHHHGGFFFFFFFGGGGGGFEEDDDEEEEDCDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBCBBBBBBBBCCCBBBAAAAAAAAAA@@@@?????@@@@@@???>>=>>>>>>>>>>>>>>>>>>>>>>>>>>NNNNNNMMMMMMMNNNNNNNNNNMMNNNNNNNOOOOPPONOOOPPPPPQQQQQPPPPQQQQQQQQRRRRRRSSSSTTTTTTTTSSSSSSSSSTTTTTUTTTTUVVVVVVVVVVVVVVWVWWWWWXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYZZZZZZZ[[[ZZ[[[Z[[[[Z[[[\[[\\\\\\\\\\]]]\]\\\]\]]^^^^^^^^^^^^^^^^____```````__`````aaaaaaaaaaabbbbbbbaabbbbbbccccbcddddddddddeedccdddddddcccdddddddeeeeeeeeeeeeeeeeeeedefededdddc^RA2& "!!'.137?C=2)-57;><99?EJ?6L]_UIDCDDB??BDQ_^UJLOLGCGKKFCELU_ec[TSPLIHIMS^dc]Y\dnsuuttuuvz‰‘••—™›™”…~wwƒŒ’—˜–“‘‘‘’•–•••–––˜˜–”’’“““““”””•————————˜˜˜˜˜˜——˜˜—–••––•––”“’’’’’“’’’“““‘ŒŒŒŠ‰ˆ‡‡†…ƒ~~}||{yuqomkjhfedb`][YWWVUTRLIIGEDBA?=;976410/--,++**(&! $$ &@]msppqqqpqqppppqqqqqpppppppqqppopppqpoooppppooonnnnnnnmnnnnnmnnnnnnnnnnnnnnnmmmmmmmmmmmmlllkkkklllllllllkkkkkjjkkjjjjjjjjjjjjikkkkkkkkkkkkkjjiiihhhiiiijlkjiijjihhhiihiiimnigfeegkkf\PE8-**)%$))"#',+'%$$$$&(*./-($$&('+35435882+2957951/,)(('$ .5*" *0/*&)***)'),0340(""%&$!!#)-/-$!&"!(,,,+)" &,.$" #)1+$)))'#$+*(66' &)&$'09<60,-121-&&-694.)++(()*./' !%'5EJLLJHHJKJJJJIIIHHHHHHHIHHHHHHHHHHHHGGFFFFGGGGGFFFFGFEDDDDDDDDCCDDCCCCDDDDDDCCCCCCCCCCCCCDDCCCCBBBBBCCCBBBBBAAA@@@@AAAAAA@@@@@@@@@@@@@??????>>>>>>>>=>>>==============NNNNNNMMMMMLMOOONMNNNNMLLMNNNOOOOOPOPPONOOOOPPPPQQQQQPPPPQQRQQPPRRRRRRRSSSTTTSSSSSTSSSSSSSSSTTTTTTUUUUVVVVVVVVVVVVVVVVVVWWWXXXXXXXXXXXXYYYYYYYYYYYXXYYYYYYZZZZZZZ[ZZZZ[[[ZZZZZ[[[\\\\\\\\\\\\\\]]]\\\[\\\\]^^^^^^^^^______^____```_______`__``a``abaaaaabbbbbbbaabbbbbbbbbbbccccccccddddedcccdddddddccdddddddeeeeeeeeeeeeeeeeeeedeeddeddeebXI9-%!##!!$*003<DD6,,07;@C><?EHF=:P^\RIHGEB>=DJKS[TLFLRPHAEGD@AIT_ee_XTWRJIILR[efb\]gnsvwvwz}…•›žœš”‡……Š•œž žš–••—™™™™šœœš™™™š›œš˜˜™™˜—––—˜™šš›ššššš››šš™™˜šœœ™–•–——–˜˜••••”“““““““””“‘‘‘‘‘ŽŒŒŠ‰ˆˆˆ‡…‚€~}}{xtqpnmkigfeda^[YXYYVURMJJHFEDBA?<:875320.-,,+++)&""&%"0Ohusqpqqpqqpppqqqqqqqpppoppqqpoooppqpooooooooooooooonnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmlllllllllllllllllmmmlkkkkjkkkjijjjjkjjjjjijjkkkkkkkkkjjjiiiiihiiiijjkjiiijkihghiiihjmlg^ZY[_dhdWG:5,""%&''*'"')&""""#%%&)-.-)%#$&%)12259<<707<88973/+))(%! "06+&&+-,(%'()'&&).487/$!%% )111-# # ,.+*)' '*'" " %*.'!&'(**%$,+)42#"))##*7<;6568752/')3::0+*-+$#(/-&!"" %3>HMNKIIIIIIIIIIHHHHHHHIHHHIIIIIIHGHGGFFFGGGGGGGFFFGFEEDDDEDDDDDCCCDDDDDDDDDCCCCCCCCCCCCCDDCCCCBCCCCCCCCCBABAAAA@@@AAAAAA@@@@@@@@@@@@@???????>>>>>>=======<<<<<<<<<<<<NNNNNNNNNMMMMNONNMNNNNMMMMNNNNNNNOOOOOOOOPOOPPPPPPQQQPPPPQQRRQPPRRRRRRRSSSTTTSSSSSTTTTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVVWWWWXXXXXXXXXXXYYYYYYYYYYYYYYYYYZZZZZZZZZZ[[[[[[Z[[[[Z[[\[\\\\\\\\\\\\\]]]]\\\[\\\]]^^^^^^^^^^^^^_______``````````````aaaaaaaaaabbbbbbbbbbbbbbbbbbbbccbbbbbbbbcccdddddccccccddddddddddeeeeeeeeeeeeeeeeeeeeeedefeefd^QB5*$" #$#!"$'*+.6BF@3,027>CE==EJI><BNVWQKMKE@?BJONOOLKLOPLF@@>=AIS^ffaZWWWRKHJQ[a`]\dlrsu{‚†‹’”˜œžžœ›šš—•““•™ ¢££¢Ÿœž ¡ ¡¡¡ žžžžžœœ›››œœ›››››œš˜———˜˜™™—–––•””•””””“““”“’’’’‘ŽŒŒŒŒŠ‰‰‰ˆ†„‚€€~}zwtrponlihgfdb_\ZYXWVUSNLJHFDCBA?=<:976310/.--,,*'$$'&#'@]rttrtsprsqqqqqqqqqqpppoppqqpoooppqqpppooooooooooooonnnnnnnnnnnnnnnmnnnnnnnnnnnnnmmmmmmmmmmmmmllllllmmllkkkkkkkkjjkkklllkkkkjiijjjjjjjjjiiiiiiiiiiiiiiiihhhijjjhhhijkiih^SLFELU[YN;,&+*"#'+,+*%!"""#%&()))))))(&$#$'*..08<<>=:<<978764.*(%"%/3,)'''''&&&'$%*,.389/%"%$ '/531,%,-((& !'%!#! "%)*##&+..(&*++/,$ ')(%(09<:99:97420-1<;3+*-+& #)-%"&)(" $$# ! "-;HOPLHFHIJJIIIIIIIIIIIIIIIIIIIHHHHGGFFGGGGGGGGGFFGGFEEDEEEEDDDCCDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBCBBBBAAAAA@@AAAAAA@@@@@@@@@@@@@???????>>>>>>=======<<<<<<<<<<<<NNNNNNNNNMMMMNNNNNNNNNMMMNNNNNNNNNOOOOOOOOOOOPPPPPPQQPPPPPQRRQQQRRRRRRRSSSTUTSSSSSTTTTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZ[[ZYYYYZ[ZZZZZZZZZZZZ[[[[\\\\\\\\\\\\\]]]]]\\\[\\\]]^^^^^^^^^^^^^^^___````````````````aaaaaaaaabbbbbbbbbbbbbbbbbbbccccbbbbbbbbbcccddddccccccddddddddeeeeeeeeeeeeeeeeeeeeefeeeeffefcYI9-%! !"" "%'('.:HH=0-679@C@9<INI79FRWUOLPLD>>ELPPMIJMQROJE@;9;CNYdje[RQRPLJKNT[_YW]my{|~…Š“™œžžŸŸœ—’‘”—›Ÿ¢¥§¨¨¨¦¤£££¤¤¥¦¦¥¦¦¥£¢¡¡¢¢£¢¡ ŸžžžžžŸ ¡ ŸŸžžœžžœš™™™ššš›™˜—–––——–––””“”––”“““”’ŒŒ‹Š‰ˆ†„‚€€}zwtrrqonkiigec`^\ZXVVVTPMKIGEDCB@?=<;:85310//..-+(''(&# /Nisustsprsrqrrrrrqqqqppopppppppoppppppppppooooooppppoooooooooonnnnmmmmmmmmmmmmmmmmmmllmmmmmllmllllllmlllllkkllllkkkllllllllkkjjjjjjjjjjjjjhhijjjjjjiihhhhghiiiiiiihijj_KCCKF?HQSE4)$&/1,,./.,+($$%&()+*))+/1342.*&"!#(,/++48:>CEA:41/.-+&#$##*-**(#"%))%%&"&...277-#!#"#+4730-("#/.''$ $'"!!$(&!!&140)$(*++(##*)'&,1/2542023202;A@90(')$!&*'"%&(''!#%''%" !"*6DMOLGGIMLJJJIIIHHIIIIIHIIIIIIHHHHGGGGGHGGGHGGGGGGFEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBCCCCBBBBBBAAAAAAAAAAAA@@@@@@@@@@@@@???????>>>>>>=======<===========NNNONNNNNNNNNNNNNNNNNNNNNMMMNNNNNONOOOOOOOOOPPPPPPPQQPPPPPQRRQQQRRRRRRRSSSTUTSSSSSTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZZ[[[ZZZZZ[[[[[[[ZZZZ[Z[[\\\]]\\\\\\\\]]]]]]]\\\[\\\]]^^^^^^^^^^^^]]^___``````````````````aabbaaabbbbbbbbbbbbbbbbbbccccccccccccccdddddddccccccdddddddeeeeeeeeeeeeeeeeeeeeeeffefggfdd^SB2' #%')3@ID7/0:;=BA;7<HKD6;KVZTKHMIB?CLOPNKIKORPKGB>88@HR]fh_SKJLHGIRY[^__ckx~}}‚‰Ž’”˜Ÿ ŸŸ“ŽŽ’—œ¡¥¨ª¯®«©©©©ªª©©ªªª«ª©¨¦¥¦§§§§¥¤¤££¢¢¡¡ ¡¢¢¡¡¢¢£¢¡ ŸžžžŸ Ÿžœœœœ››››šš™˜—˜˜™™—–•””•——–••••“ŽŽŽŽŽŒŠŠ‰ˆ†„}zwutsrqomkjifdb`^[XVUVUQOMKIGEDCB@?>=<:753110/..,*))('$" ;Yltssrqrsrrrrrrrrrqqqppppppppppppppqqpppppoppppppppooooooooonnnnmmmmmmmmmnnnnnmmmmmmmmmmmmmmmllllllllllllkklllllkklmmmmmllkkkkkkkkkkkkkkkiijjjjjjjjjiiihhiiiiiijihgf`SFOZZQHIC5/+'&(.0-+****(&%$%%'))'#"$&+27961,'$#"(3.+37:?EHA82-)(&$"!"!#&()*)! %++#!##)1.+043)"!! &-230/.,& &.,&%""&& "(% !+9;0&"%'''&##)'&'-1-151+&,2301?E?5+'%#")*"!%%%')(# diff --git a/media/testdata/faces_I400.jpg b/media/testdata/faces_I400.jpg Binary files differnew file mode 100644 index 0000000..c928f00 --- /dev/null +++ b/media/testdata/faces_I400.jpg diff --git a/media/testdata/faces_I411.jpg b/media/testdata/faces_I411.jpg Binary files differnew file mode 100644 index 0000000..600c122 --- /dev/null +++ b/media/testdata/faces_I411.jpg diff --git a/media/testdata/faces_I420.jpg b/media/testdata/faces_I420.jpg Binary files differnew file mode 100644 index 0000000..10c5332 --- /dev/null +++ b/media/testdata/faces_I420.jpg diff --git a/media/testdata/faces_I422.jpg b/media/testdata/faces_I422.jpg Binary files differnew file mode 100644 index 0000000..9907aa1 --- /dev/null +++ b/media/testdata/faces_I422.jpg diff --git a/media/testdata/faces_I444.jpg b/media/testdata/faces_I444.jpg Binary files differnew file mode 100644 index 0000000..3422755 --- /dev/null +++ b/media/testdata/faces_I444.jpg diff --git a/media/webrtc/OWNERS b/media/webrtc/OWNERS index 9a3546e..aef5939 100644 --- a/media/webrtc/OWNERS +++ b/media/webrtc/OWNERS @@ -1,3 +1,3 @@ mflodman@webrtc.org +pbos@webrtc.org pthatcher@webrtc.org -wu@webrtc.org diff --git a/media/webrtc/fakewebrtcvideocapturemodule.h b/media/webrtc/fakewebrtcvideocapturemodule.h index 347e4b7..82b5cdd 100644 --- a/media/webrtc/fakewebrtcvideocapturemodule.h +++ b/media/webrtc/fakewebrtcvideocapturemodule.h @@ -44,76 +44,72 @@ class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule { running_(false), delay_(0) { } - virtual int32_t Version(char* version, - uint32_t& remaining_buffer_in_bytes, - uint32_t& position) const { + virtual int32_t TimeUntilNextProcess() OVERRIDE { return 0; } - virtual int32_t TimeUntilNextProcess() { + virtual int32_t Process() OVERRIDE { return 0; } - virtual int32_t Process() { - return 0; - } - virtual int32_t ChangeUniqueId(const int32_t id) { + virtual int32_t ChangeUniqueId(const int32_t id) OVERRIDE { id_ = id; return 0; } virtual void RegisterCaptureDataCallback( - webrtc::VideoCaptureDataCallback& callback) { + webrtc::VideoCaptureDataCallback& callback) OVERRIDE { callback_ = &callback; } - virtual void DeRegisterCaptureDataCallback() { callback_ = NULL; } - virtual void RegisterCaptureCallback(webrtc::VideoCaptureFeedBack& callback) { + virtual void DeRegisterCaptureDataCallback() OVERRIDE { callback_ = NULL; } + virtual void RegisterCaptureCallback( + webrtc::VideoCaptureFeedBack& callback) OVERRIDE { // Not implemented. } - virtual void DeRegisterCaptureCallback() { + virtual void DeRegisterCaptureCallback() OVERRIDE { // Not implemented. } - virtual void SetCaptureDelay(int32_t delay) { delay_ = delay; } - virtual int32_t CaptureDelay() { return delay_; } - virtual void EnableFrameRateCallback(const bool enable) { + virtual void SetCaptureDelay(int32_t delay) OVERRIDE { delay_ = delay; } + virtual int32_t CaptureDelay() OVERRIDE { return delay_; } + virtual void EnableFrameRateCallback(const bool enable) OVERRIDE { // not implemented } - virtual void EnableNoPictureAlarm(const bool enable) { + virtual void EnableNoPictureAlarm(const bool enable) OVERRIDE { // not implemented } virtual int32_t StartCapture( - const webrtc::VideoCaptureCapability& cap) { + const webrtc::VideoCaptureCapability& cap) OVERRIDE { if (running_) return -1; cap_ = cap; running_ = true; return 0; } - virtual int32_t StopCapture() { + virtual int32_t StopCapture() OVERRIDE { running_ = false; return 0; } - virtual const char* CurrentDeviceName() const { + virtual const char* CurrentDeviceName() const OVERRIDE { return NULL; // not implemented } - virtual bool CaptureStarted() { + virtual bool CaptureStarted() OVERRIDE { return running_; } virtual int32_t CaptureSettings( - webrtc::VideoCaptureCapability& settings) { + webrtc::VideoCaptureCapability& settings) OVERRIDE { if (!running_) return -1; settings = cap_; return 0; } virtual int32_t SetCaptureRotation( - webrtc::VideoCaptureRotation rotation) { + webrtc::VideoCaptureRotation rotation) OVERRIDE { return -1; // not implemented } virtual VideoCaptureEncodeInterface* GetEncodeInterface( - const webrtc::VideoCodec& codec) { + const webrtc::VideoCodec& codec) OVERRIDE { return NULL; // not implemented } - virtual int32_t AddRef() { + virtual int32_t AddRef() OVERRIDE { return 0; } - virtual int32_t Release() { + virtual int32_t Release() OVERRIDE { delete this; return 0; } diff --git a/media/webrtc/fakewebrtcvideoengine.h b/media/webrtc/fakewebrtcvideoengine.h index 5cba380..ec4b9c6 100644 --- a/media/webrtc/fakewebrtcvideoengine.h +++ b/media/webrtc/fakewebrtcvideoengine.h @@ -335,6 +335,7 @@ class FakeWebRtcVideoEngine unsigned int send_nack_bitrate_; unsigned int send_bandwidth_; unsigned int receive_bandwidth_; + // Bandwidth to deduct from estimated uplink capacity. unsigned int reserved_transmit_bitrate_bps_; bool suspend_below_min_bitrate_; webrtc::CpuOveruseObserver* overuse_observer_; diff --git a/media/webrtc/fakewebrtcvoiceengine.h b/media/webrtc/fakewebrtcvoiceengine.h index f731b8d..52a50ff 100644 --- a/media/webrtc/fakewebrtcvoiceengine.h +++ b/media/webrtc/fakewebrtcvoiceengine.h @@ -43,10 +43,7 @@ #ifdef USE_WEBRTC_DEV_BRANCH #include "webrtc/modules/audio_processing/include/audio_processing.h" #endif - -namespace webrtc { -class ViENetwork; -} +#include "webrtc/video_engine/include/vie_network.h" namespace cricket { @@ -64,6 +61,12 @@ static const int kFakeDeviceId = 0; static const int kFakeDeviceId = 1; #endif +static const int kOpusBandwidthNb = 4000; +static const int kOpusBandwidthMb = 6000; +static const int kOpusBandwidthWb = 8000; +static const int kOpusBandwidthSwb = 12000; +static const int kOpusBandwidthFb = 20000; + // Verify the header extension ID, if enabled, is within the bounds specified in // [RFC5285]: 1-14 inclusive. #define WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id) \ @@ -183,6 +186,7 @@ class FakeWebRtcVoiceEngine file(false), vad(false), codec_fec(false), + max_encoding_bandwidth(0), red(false), nack(false), media_processor_registered(false), @@ -212,6 +216,7 @@ class FakeWebRtcVoiceEngine bool file; bool vad; bool codec_fec; + int max_encoding_bandwidth; bool red; bool nack; bool media_processor_registered; @@ -308,6 +313,9 @@ class FakeWebRtcVoiceEngine bool GetCodecFEC(int channel) { return channels_[channel]->codec_fec; } + int GetMaxEncodingBandwidth(int channel) { + return channels_[channel]->max_encoding_bandwidth; + } bool GetNACK(int channel) { return channels_[channel]->nack; } @@ -316,6 +324,8 @@ class FakeWebRtcVoiceEngine } webrtc::ViENetwork* GetViENetwork(int channel) { WEBRTC_ASSERT_CHANNEL(channel); + // WARNING: This pointer is for verification purposes only. Calling + // functions on it may result in undefined behavior! return channels_[channel]->vie_network; } int GetVideoChannel(int channel) { @@ -488,8 +498,6 @@ class FakeWebRtcVoiceEngine WEBRTC_STUB(LastError, ()); WEBRTC_STUB(SetOnHoldStatus, (int, bool, webrtc::OnHoldModes)); WEBRTC_STUB(GetOnHoldStatus, (int, bool&, webrtc::OnHoldModes&)); - WEBRTC_STUB(SetNetEQPlayoutMode, (int, webrtc::NetEqModes)); - WEBRTC_STUB(GetNetEQPlayoutMode, (int, webrtc::NetEqModes&)); // webrtc::VoECodec WEBRTC_FUNC(NumOfCodecs, ()) { @@ -625,10 +633,11 @@ class FakeWebRtcVoiceEngine } WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled, webrtc::VadModes& mode, bool& disabledDTX)); + #ifdef USE_WEBRTC_DEV_BRANCH WEBRTC_FUNC(SetFECStatus, (int channel, bool enable)) { WEBRTC_CHECK_CHANNEL(channel); - if (strcmp(channels_[channel]->send_codec.plname, "opus")) { + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { // Return -1 if current send codec is not Opus. // TODO(minyue): Excludes other codecs if they support inband FEC. return -1; @@ -641,6 +650,25 @@ class FakeWebRtcVoiceEngine enable = channels_[channel]->codec_fec; return 0; } + + WEBRTC_FUNC(SetOpusMaxPlaybackRate, (int channel, int frequency_hz)) { + WEBRTC_CHECK_CHANNEL(channel); + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { + // Return -1 if current send codec is not Opus. + return -1; + } + if (frequency_hz <= 8000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthNb; + else if (frequency_hz <= 12000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthMb; + else if (frequency_hz <= 16000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthWb; + else if (frequency_hz <= 24000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthSwb; + else + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthFb; + return 0; + } #endif // USE_WEBRTC_DEV_BRANCH // webrtc::VoEDtmf @@ -999,6 +1027,11 @@ class FakeWebRtcVoiceEngine WEBRTC_CHECK_CHANNEL(channel); channels_[channel]->vie_network = vie_network; channels_[channel]->video_channel = video_channel; + if (vie_network) { + // The interface is released here to avoid leaks. A test should not + // attempt to call functions on the interface stored in the channel. + vie_network->Release(); + } return 0; } diff --git a/media/webrtc/webrtcmediaengine.cc b/media/webrtc/webrtcmediaengine.cc index 252b4e6..cf0fcdf 100644 --- a/media/webrtc/webrtcmediaengine.cc +++ b/media/webrtc/webrtcmediaengine.cc @@ -50,7 +50,6 @@ class WebRtcMediaEngine : WebRtcVideoDecoderFactory* decoder_factory) { voice_.SetAudioDeviceModule(adm, adm_sc); video_.SetVoiceEngine(&voice_); - video_.EnableTimedRender(); video_.SetExternalEncoderFactory(encoder_factory); video_.SetExternalDecoderFactory(decoder_factory); } @@ -65,8 +64,9 @@ class WebRtcMediaEngine2 : WebRtcVideoEncoderFactory* encoder_factory, WebRtcVideoDecoderFactory* decoder_factory) { voice_.SetAudioDeviceModule(adm, adm_sc); + video_.SetExternalDecoderFactory(decoder_factory); + video_.SetExternalEncoderFactory(encoder_factory); video_.SetVoiceEngine(&voice_); - video_.EnableTimedRender(); } }; #endif // WEBRTC_CHROMIUM_BUILD @@ -85,7 +85,6 @@ cricket::MediaEngineInterface* CreateWebRtcMediaEngine( adm, adm_sc, encoder_factory, decoder_factory); } #endif // WEBRTC_CHROMIUM_BUILD - // This is just to get a diff to run pulse. return new cricket::WebRtcMediaEngine( adm, adm_sc, encoder_factory, decoder_factory); } diff --git a/media/webrtc/webrtcmediaengine.h b/media/webrtc/webrtcmediaengine.h index b906f5d..df517ee 100644 --- a/media/webrtc/webrtcmediaengine.h +++ b/media/webrtc/webrtcmediaengine.h @@ -122,9 +122,6 @@ class DelegatingWebRtcMediaEngine : public cricket::MediaEngineInterface { virtual bool SetAudioOptions(const AudioOptions& options) OVERRIDE { return delegate_->SetAudioOptions(options); } - virtual bool SetVideoOptions(const VideoOptions& options) OVERRIDE { - return delegate_->SetVideoOptions(options); - } virtual bool SetAudioDelayOffset(int offset) OVERRIDE { return delegate_->SetAudioDelayOffset(offset); } @@ -151,9 +148,6 @@ class DelegatingWebRtcMediaEngine : public cricket::MediaEngineInterface { virtual bool SetLocalMonitor(bool enable) OVERRIDE { return delegate_->SetLocalMonitor(enable); } - virtual bool SetLocalRenderer(VideoRenderer* renderer) OVERRIDE { - return delegate_->SetLocalRenderer(renderer); - } virtual const std::vector<AudioCodec>& audio_codecs() OVERRIDE { return delegate_->audio_codecs(); } diff --git a/media/webrtc/webrtcpassthroughrender.h b/media/webrtc/webrtcpassthroughrender.h index a432776..8d8c488 100644 --- a/media/webrtc/webrtcpassthroughrender.h +++ b/media/webrtc/webrtcpassthroughrender.h @@ -41,26 +41,20 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { WebRtcPassthroughRender(); virtual ~WebRtcPassthroughRender(); - virtual int32_t Version(int8_t* version, - uint32_t& remainingBufferInBytes, - uint32_t& position) const { + virtual int32_t ChangeUniqueId(const int32_t id) OVERRIDE { return 0; } - virtual int32_t ChangeUniqueId(const int32_t id) { - return 0; - } - - virtual int32_t TimeUntilNextProcess() { return 0; } + virtual int32_t TimeUntilNextProcess() OVERRIDE { return 0; } - virtual int32_t Process() { return 0; } + virtual int32_t Process() OVERRIDE { return 0; } - virtual void* Window() { + virtual void* Window() OVERRIDE { rtc::CritScope cs(&render_critical_); return window_; } - virtual int32_t ChangeWindow(void* window) { + virtual int32_t ChangeWindow(void* window) OVERRIDE { rtc::CritScope cs(&render_critical_); window_ = window; return 0; @@ -70,64 +64,60 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t stream_id, const uint32_t zOrder, const float left, const float top, - const float right, const float bottom); + const float right, const float bottom) OVERRIDE; - virtual int32_t DeleteIncomingRenderStream(const uint32_t stream_id); + virtual int32_t DeleteIncomingRenderStream(const uint32_t stream_id) OVERRIDE; virtual int32_t AddExternalRenderCallback( const uint32_t stream_id, - webrtc::VideoRenderCallback* render_object); + webrtc::VideoRenderCallback* render_object) OVERRIDE; virtual int32_t GetIncomingRenderStreamProperties( const uint32_t stream_id, uint32_t& zOrder, float& left, float& top, - float& right, float& bottom) const { + float& right, float& bottom) const OVERRIDE { return -1; } - virtual uint32_t GetIncomingFrameRate( - const uint32_t stream_id) { + virtual uint32_t GetIncomingFrameRate(const uint32_t stream_id) OVERRIDE { return 0; } - virtual uint32_t GetNumIncomingRenderStreams() const { + virtual uint32_t GetNumIncomingRenderStreams() const OVERRIDE { return static_cast<uint32_t>(stream_render_map_.size()); } - virtual bool HasIncomingRenderStream(const uint32_t stream_id) const; + virtual bool HasIncomingRenderStream(const uint32_t stream_id) const OVERRIDE; virtual int32_t RegisterRawFrameCallback( const uint32_t stream_id, - webrtc::VideoRenderCallback* callback_obj) { + webrtc::VideoRenderCallback* callback_obj) OVERRIDE { return -1; } virtual int32_t GetLastRenderedFrame( const uint32_t stream_id, - webrtc::I420VideoFrame &frame) const { + webrtc::I420VideoFrame &frame) const OVERRIDE { return -1; } - virtual int32_t StartRender( - const uint32_t stream_id); + virtual int32_t StartRender(const uint32_t stream_id) OVERRIDE; - virtual int32_t StopRender( - const uint32_t stream_id); + virtual int32_t StopRender(const uint32_t stream_id) OVERRIDE; - virtual int32_t ResetRender() { return 0; } + virtual int32_t ResetRender() OVERRIDE { return 0; } - virtual webrtc::RawVideoType PreferredVideoType() const; + virtual webrtc::RawVideoType PreferredVideoType() const OVERRIDE; - virtual bool IsFullScreen() { return false; } + virtual bool IsFullScreen() OVERRIDE { return false; } virtual int32_t GetScreenResolution(uint32_t& screenWidth, - uint32_t& screenHeight) const { + uint32_t& screenHeight) const OVERRIDE { return -1; } - virtual uint32_t RenderFrameRate( - const uint32_t stream_id) { + virtual uint32_t RenderFrameRate(const uint32_t stream_id) OVERRIDE { return 0; } @@ -135,11 +125,12 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t stream_id, const float left, const float top, const float right, - const float bottom) { + const float bottom) OVERRIDE { return -1; } - virtual int32_t SetExpectedRenderDelay(uint32_t stream_id, int32_t delay_ms) { + virtual int32_t SetExpectedRenderDelay(uint32_t stream_id, + int32_t delay_ms) OVERRIDE { return -1; } @@ -148,22 +139,22 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const unsigned int zOrder, const float left, const float top, const float right, - const float bottom) { + const float bottom) OVERRIDE { return -1; } - virtual int32_t SetTransparentBackground(const bool enable) { + virtual int32_t SetTransparentBackground(const bool enable) OVERRIDE { return -1; } - virtual int32_t FullScreenRender(void* window, const bool enable) { + virtual int32_t FullScreenRender(void* window, const bool enable) OVERRIDE { return -1; } virtual int32_t SetBitmap(const void* bitMap, const uint8_t pictureId, const void* colorKey, const float left, const float top, - const float right, const float bottom) { + const float right, const float bottom) OVERRIDE { return -1; } @@ -173,27 +164,27 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t textColorRef, const uint32_t backgroundColorRef, const float left, const float top, - const float right, const float bottom) { + const float right, const float bottom) OVERRIDE { return -1; } virtual int32_t SetStartImage( const uint32_t stream_id, - const webrtc::I420VideoFrame& videoFrame) { + const webrtc::I420VideoFrame& videoFrame) OVERRIDE { return -1; } virtual int32_t SetTimeoutImage( const uint32_t stream_id, const webrtc::I420VideoFrame& videoFrame, - const uint32_t timeout) { + const uint32_t timeout) OVERRIDE { return -1; } virtual int32_t MirrorRenderStream(const int renderId, const bool enable, const bool mirrorXAxis, - const bool mirrorYAxis) { + const bool mirrorYAxis) OVERRIDE { return -1; } diff --git a/media/webrtc/webrtcvideoengine.cc b/media/webrtc/webrtcvideoengine.cc index 83b1177..b2533b3 100644 --- a/media/webrtc/webrtcvideoengine.cc +++ b/media/webrtc/webrtcvideoengine.cc @@ -64,6 +64,22 @@ #include "webrtc/experiments.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +namespace { + +template <class T> +bool Changed(cricket::Settable<T> proposed, + cricket::Settable<T> original) { + return proposed.IsSet() && proposed != original; +} + +template <class T> +bool Changed(cricket::Settable<T> proposed, + cricket::Settable<T> original, + T* value) { + return proposed.Get(value) && proposed != original; +} + +} // namespace namespace cricket { @@ -81,6 +97,9 @@ const int kMaxVideoBitrate = 2000; const int kCpuMonitorPeriodMs = 2000; // 2 seconds. +// TODO(pthatcher): Figure out what the proper value here is, or if we +// can just remove this altogether. +static const int kDefaultRenderDelayMs = 100; static const int kDefaultLogSeverity = rtc::LS_WARNING; @@ -926,8 +945,6 @@ void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper, initialized_ = false; SetTraceFilter(SeverityToFilter(kDefaultLogSeverity)); render_module_.reset(new WebRtcPassthroughRender()); - local_renderer_w_ = local_renderer_h_ = 0; - local_renderer_ = NULL; capture_started_ = false; decoder_factory_ = NULL; encoder_factory_ = NULL; @@ -953,6 +970,9 @@ void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper, LOG(LS_ERROR) << "Failed to initialize list of supported codec types"; } + // Consider jitter, packet loss, etc when rendering. This will + // theoretically make rendering more smooth. + EnableTimedRender(); // Load our RTP Header extensions. rtp_header_extensions_.push_back( @@ -1059,10 +1079,6 @@ int WebRtcVideoEngine::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } -bool WebRtcVideoEngine::SetOptions(const VideoOptions &options) { - return true; -} - bool WebRtcVideoEngine::SetDefaultEncoderConfig( const VideoEncoderConfig& config) { return SetDefaultCodec(config.max_codec); @@ -1109,12 +1125,6 @@ WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel( return channel; } -bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) { - local_renderer_w_ = local_renderer_h_ = 0; - local_renderer_ = renderer; - return true; -} - const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const { return video_codecs_; } @@ -1568,6 +1578,7 @@ WebRtcVideoMediaChannel::WebRtcVideoMediaChannel( remb_enabled_(false), render_started_(false), first_receive_ssrc_(kSsrcUnset), + receiver_report_ssrc_(kSsrcUnset), num_unsignalled_recv_channels_(0), send_rtx_type_(-1), send_red_type_(-1), @@ -1580,7 +1591,19 @@ WebRtcVideoMediaChannel::WebRtcVideoMediaChannel( bool WebRtcVideoMediaChannel::Init() { const uint32 ssrc_key = 0; - return CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_); + bool result = CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_); + if (!result) { + return false; + } + if (voice_channel_) { + WebRtcVoiceMediaChannel* voice_channel = + static_cast<WebRtcVoiceMediaChannel*>(voice_channel_); + if (!voice_channel->SetupSharedBandwidthEstimation( + engine()->vie()->engine(), default_channel_id_)) { + return false; + } + } + return true; } WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() { @@ -1752,6 +1775,35 @@ bool WebRtcVideoMediaChannel::SetSendCodecs( return true; } +bool WebRtcVideoMediaChannel::MaybeRegisterExternalEncoder( + WebRtcVideoChannelSendInfo* send_channel, + const webrtc::VideoCodec& codec) { + // Codec type not supported or encoder already registered, so + // nothing to do. + if (!engine()->IsExternalEncoderCodecType(codec.codecType) + || send_channel->IsEncoderRegistered(codec.plType)) { + return true; + } + + webrtc::VideoEncoder* encoder = + engine()->CreateExternalEncoder(codec.codecType); + if (!encoder) { + // No encoder factor, so nothing to do. + return true; + } + + const int channel_id = send_channel->channel_id(); + if (engine()->vie()->ext_codec()->RegisterExternalSendCodec( + channel_id, codec.plType, encoder, false) != 0) { + LOG_RTCERR2(RegisterExternalSendCodec, channel_id, codec.plName); + engine()->DestroyExternalEncoder(encoder); + return false; + } + + send_channel->RegisterEncoder(codec.plType, encoder); + return true; +} + bool WebRtcVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) { if (!send_codec_) { return false; @@ -1861,18 +1913,9 @@ bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) { } WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrcKey(ssrc_key); - // Set the send (local) SSRC. // If there are multiple send SSRCs, we can only set the first one here, and // the rest of the SSRC(s) need to be set after SetSendCodec has been called - // (with a codec requires multiple SSRC(s)). - if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, - sp.first_ssrc()) != 0) { - LOG_RTCERR2(SetLocalSSRC, channel_id, sp.first_ssrc()); - return false; - } - - // Set the corresponding RTX SSRC. - if (!SetLocalRtxSsrc(channel_id, sp, sp.first_ssrc(), 0)) { + if (!SetLimitedNumberOfSendSsrcs(channel_id, sp, 1)) { return false; } @@ -1883,21 +1926,9 @@ bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) { return false; } - // At this point the channel's local SSRC has been updated. If the channel is - // the default channel make sure that all the receive channels are updated as - // well. Receive channels have to have the same SSRC as the default channel in - // order to send receiver reports with this SSRC. + // Use the SSRC of the default channel in the RTCP receiver reports. if (IsDefaultChannelId(channel_id)) { - for (RecvChannelMap::const_iterator it = recv_channels_.begin(); - it != recv_channels_.end(); ++it) { - WebRtcVideoChannelRecvInfo* info = it->second; - int channel_id = info->channel_id(); - if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, - sp.first_ssrc()) != 0) { - LOG_RTCERR1(SetLocalSSRC, it->first); - return false; - } - } + SetReceiverReportSsrc(sp.first_ssrc()); } send_channel->set_stream_params(sp); @@ -2018,25 +2049,6 @@ bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) { return false; } - // Get the default renderer. - VideoRenderer* default_renderer = NULL; - if (InConferenceMode()) { - // The recv_channels_ size start out being 1, so if it is two here - // this is the first receive channel created (default_channel_id_ - // is not used for receiving in a conference call). This means - // that the renderer stored inside default_channel_id_ should be - // used for the just created channel. - if (recv_channels_.size() == 2 && GetDefaultRecvChannel()) { - GetDefaultRenderer(&default_renderer); - } - } - - // The first recv stream reuses the default renderer (if a default renderer - // has been set). - if (default_renderer) { - SetRenderer(sp.first_ssrc(), default_renderer); - } - LOG(LS_INFO) << "New video stream " << sp.first_ssrc() << " registered to VideoEngine channel #" << channel_id << " and connected to channel #" @@ -2228,6 +2240,9 @@ bool WebRtcVideoMediaChannel::GetSendChannelSsrcKey(uint32 local_ssrc, return true; } if (!GetSendChannelBySsrcKey(local_ssrc)) { + // If a stream has multiple ssrcs, the local_ssrc could be any of + // them, but we use the first one (StreamParams::first_ssrc()) as + // the key. for (SendChannelMap::iterator iter = send_channels_.begin(); iter != send_channels_.end(); ++iter) { WebRtcVideoChannelSendInfo* send_channel = iter->second; @@ -2898,7 +2913,7 @@ bool WebRtcVideoMediaChannel::SetStartSendBandwidth(int bps) { } // On success, SetSendCodec() will reset |send_start_bitrate_| to |bps/1000|, - // by calling MaybeChangeBitrates. That method will also clamp the + // by calling SanitizeBitrates. That method will also clamp the // start bitrate between min and max, consistent with the override behavior // in SetMaxSendBandwidth. webrtc::VideoCodec new_codec = *send_codec_; @@ -2934,39 +2949,10 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { return true; } - // Trigger SetSendCodec to set correct noise reduction state if the option has - // changed. - bool denoiser_changed = options.video_noise_reduction.IsSet() && - (options_.video_noise_reduction != options.video_noise_reduction); - - bool leaky_bucket_changed = options.video_leaky_bucket.IsSet() && - (options_.video_leaky_bucket != options.video_leaky_bucket); - - bool buffer_latency_changed = options.buffered_mode_latency.IsSet() && - (options_.buffered_mode_latency != options.buffered_mode_latency); - - bool dscp_option_changed = (options_.dscp != options.dscp); - - bool suspend_below_min_bitrate_changed = - options.suspend_below_min_bitrate.IsSet() && - (options_.suspend_below_min_bitrate != options.suspend_below_min_bitrate); - - bool conference_mode_turned_off = false; - if (options_.conference_mode.IsSet() && options.conference_mode.IsSet() && - options_.conference_mode.GetWithDefaultIfUnset(false) && - !options.conference_mode.GetWithDefaultIfUnset(false)) { - conference_mode_turned_off = true; - } - -#ifdef USE_WEBRTC_DEV_BRANCH - bool payload_padding_changed = options.use_payload_padding.IsSet() && - options_.use_payload_padding != options.use_payload_padding; -#endif - - // Save the options, to be interpreted where appropriate. // Use options_.SetAll() instead of assignment so that unset value in options // will not overwrite the previous option value. + VideoOptions original = options_; options_.SetAll(options); // Set CPU options for all send channels. @@ -2977,38 +2963,36 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } if (send_codec_) { - bool reset_send_codec_needed = denoiser_changed; webrtc::VideoCodec new_codec = *send_codec_; + bool conference_mode_turned_off = ( + original.conference_mode.IsSet() && + options.conference_mode.IsSet() && + original.conference_mode.GetWithDefaultIfUnset(false) && + !options.conference_mode.GetWithDefaultIfUnset(false)); if (conference_mode_turned_off) { // This is a special case for turning conference mode off. // Max bitrate should go back to the default maximum value instead // of the current maximum. new_codec.maxBitrate = kAutoBandwidth; - reset_send_codec_needed = true; } // TODO(pthatcher): Remove this. We don't need 4 ways to set bitrates. int new_start_bitrate; if (options.video_start_bitrate.Get(&new_start_bitrate)) { new_codec.startBitrate = new_start_bitrate; - reset_send_codec_needed = true; } - - LOG(LS_INFO) << "Reset send codec needed is enabled? " - << reset_send_codec_needed; - if (reset_send_codec_needed) { - if (!SetSendCodec(new_codec)) { - return false; - } - LogSendCodecChange("SetOptions()"); + if (!SetSendCodec(new_codec)) { + return false; } + LogSendCodecChange("SetOptions()"); } - if (leaky_bucket_changed) { - bool enable_leaky_bucket = - options_.video_leaky_bucket.GetWithDefaultIfUnset(true); + bool enable_leaky_bucket; + if (Changed(options.video_leaky_bucket, + original.video_leaky_bucket, + &enable_leaky_bucket)) { LOG(LS_INFO) << "Leaky bucket is enabled? " << enable_leaky_bucket; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3022,10 +3006,11 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } - if (buffer_latency_changed) { - int buffer_latency = - options_.buffered_mode_latency.GetWithDefaultIfUnset( - cricket::kBufferedModeDisabled); + + int buffer_latency; + if (Changed(options.buffered_mode_latency, + original.buffered_mode_latency, + &buffer_latency)) { LOG(LS_INFO) << "Buffer latency is " << buffer_latency; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3044,17 +3029,24 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } - if (dscp_option_changed) { + + bool dscp_enabled; + if (Changed(options.dscp, original.dscp, &dscp_enabled)) { rtc::DiffServCodePoint dscp = rtc::DSCP_DEFAULT; - if (options_.dscp.GetWithDefaultIfUnset(false)) + if (dscp_enabled) { dscp = kVideoDscpValue; + } LOG(LS_INFO) << "DSCP is " << dscp; if (MediaChannel::SetDscp(dscp) != 0) { LOG(LS_WARNING) << "Failed to set DSCP settings for video channel"; } } - if (suspend_below_min_bitrate_changed) { - if (options_.suspend_below_min_bitrate.GetWithDefaultIfUnset(false)) { + + bool suspend_below_min_bitrate; + if (Changed(options.suspend_below_min_bitrate, + original.suspend_below_min_bitrate, + &suspend_below_min_bitrate)) { + if (suspend_below_min_bitrate) { LOG(LS_INFO) << "Suspend below min bitrate enabled."; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3065,14 +3057,17 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { LOG(LS_WARNING) << "Cannot disable video suspension once it is enabled"; } } + #ifdef USE_WEBRTC_DEV_BRANCH - if (payload_padding_changed) { + bool use_payload_padding; + if (Changed(options.use_payload_padding, + original.use_payload_padding, + &use_payload_padding)) { LOG(LS_INFO) << "Payload-based padding called."; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { engine()->vie()->rtp()->SetPadWithRedundantPayloads( - it->second->channel_id(), - options_.use_payload_padding.GetWithDefaultIfUnset(false)); + it->second->channel_id(), use_payload_padding); } } #endif @@ -3139,10 +3134,6 @@ bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc, return true; } -bool WebRtcVideoMediaChannel::GetDefaultRenderer(VideoRenderer** renderer) { - return GetRenderer(kDefaultChannelSsrcKey, renderer); -} - bool WebRtcVideoMediaChannel::GetVideoAdapter( uint32 ssrc, CoordinatedVideoAdapter** video_adapter) { WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc); @@ -3413,6 +3404,11 @@ bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id, return false; } + if (engine()->vie()->render()->SetExpectedRenderDelay( + channel_id, kDefaultRenderDelayMs)) { + LOG_RTCERR2(SetExpectedRenderDelay, + channel_id, kDefaultRenderDelayMs); + } if (engine_->vie()->rtp()->SetRembStatus(channel_id, kNotSending, @@ -3431,20 +3427,13 @@ bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id, return false; } - if (remote_ssrc != kDefaultChannelSsrcKey) { - // Use the same SSRC as our default channel - // (so the RTCP reports are correct). - unsigned int send_ssrc = 0; - webrtc::ViERTP_RTCP* rtp = engine()->vie()->rtp(); - if (rtp->GetLocalSSRC(default_channel_id_, send_ssrc) == -1) { - LOG_RTCERR2(GetLocalSSRC, default_channel_id_, send_ssrc); - return false; - } - if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) { - LOG_RTCERR2(SetLocalSSRC, channel_id, send_ssrc); + if (receiver_report_ssrc_ != kSsrcUnset) { + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, receiver_report_ssrc_) == -1) { + LOG_RTCERR2(SetLocalSSRC, channel_id, receiver_report_ssrc_); return false; } - } // Else this is the the default channel and we don't change the SSRC. + } // Disable color enhancement since it is a bit too aggressive. if (engine()->vie()->image()->EnableColorEnhancement(channel_id, @@ -3673,21 +3662,7 @@ bool WebRtcVideoMediaChannel::SetSendCodec( target_codec.codecSpecific.VP8.denoisingOn = enable_denoising; } - // Register external encoder if codec type is supported by encoder factory. - if (engine()->IsExternalEncoderCodecType(codec.codecType) && - !send_channel->IsEncoderRegistered(target_codec.plType)) { - webrtc::VideoEncoder* encoder = - engine()->CreateExternalEncoder(codec.codecType); - if (encoder) { - if (engine()->vie()->ext_codec()->RegisterExternalSendCodec( - channel_id, target_codec.plType, encoder, false) == 0) { - send_channel->RegisterEncoder(target_codec.plType, encoder); - } else { - LOG_RTCERR2(RegisterExternalSendCodec, channel_id, target_codec.plName); - engine()->DestroyExternalEncoder(encoder); - } - } - } + MaybeRegisterExternalEncoder(send_channel, target_codec); // Resolution and framerate may vary for different send channels. const VideoFormat& video_format = send_channel->video_format(); @@ -3698,7 +3673,8 @@ bool WebRtcVideoMediaChannel::SetSendCodec( LOG(LS_INFO) << "0x0 resolution selected. Captured frames will be dropped " << "for ssrc: " << ssrc << "."; } else { - MaybeChangeBitrates(channel_id, &target_codec); + StreamParams* send_params = send_channel->stream_params(); + SanitizeBitrates(channel_id, &target_codec); webrtc::VideoCodec current_codec; if (!engine()->vie()->codec()->GetSendCodec(channel_id, current_codec)) { // Compare against existing configured send codec. @@ -3713,6 +3689,11 @@ bool WebRtcVideoMediaChannel::SetSendCodec( return false; } + if (send_params) { + if (!SetSendSsrcs(channel_id, *send_params, target_codec)) { + return false; + } + } // NOTE: SetRtxSendPayloadType must be called after all simulcast SSRCs // are configured. Otherwise ssrc's configured after this point will use // the primary PT for RTX. @@ -3956,6 +3937,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( int screencast_min_bitrate = options_.screencast_min_bitrate.GetWithDefaultIfUnset(0); bool leaky_bucket = options_.video_leaky_bucket.GetWithDefaultIfUnset(true); + StreamParams* send_params = send_channel->stream_params(); bool reset_send_codec = target_width != cur_width || target_height != cur_height; if (vie_codec.codecType == webrtc::kVideoCodecVP8) { @@ -3979,7 +3961,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( vie_codec.codecSpecific.VP8.denoisingOn = enable_denoising; vie_codec.codecSpecific.VP8.frameDroppingOn = vp8_frame_dropping; } - MaybeChangeBitrates(channel_id, &vie_codec); + SanitizeBitrates(channel_id, &vie_codec); if (engine()->vie()->codec()->SetSendCodec(channel_id, vie_codec) != 0) { LOG_RTCERR1(SetSendCodec, channel_id); @@ -4001,6 +3983,13 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id, leaky_bucket); } + // TODO(sriniv): SetSendCodec already sets ssrc's like below. + // Consider removing. + if (send_params) { + if (!SetSendSsrcs(channel_id, *send_params, target_codec)) { + return false; + } + } if (reset) { *reset = true; } @@ -4010,7 +3999,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( return true; } -void WebRtcVideoMediaChannel::MaybeChangeBitrates( +void WebRtcVideoMediaChannel::SanitizeBitrates( int channel_id, webrtc::VideoCodec* codec) { codec->minBitrate = GetBitrate(codec->minBitrate, kMinVideoBitrate); codec->startBitrate = GetBitrate(codec->startBitrate, kStartVideoBitrate); @@ -4047,7 +4036,6 @@ void WebRtcVideoMediaChannel::MaybeChangeBitrates( codec->startBitrate = current_target_bitrate; } } - } void WebRtcVideoMediaChannel::OnMessage(rtc::Message* msg) { @@ -4156,21 +4144,55 @@ bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter, return SetHeaderExtension(setter, channel_id, extension); } -bool WebRtcVideoMediaChannel::SetLocalRtxSsrc(int channel_id, - const StreamParams& send_params, - uint32 primary_ssrc, - int stream_idx) { - uint32 rtx_ssrc = 0; - bool has_rtx = send_params.GetFidSsrc(primary_ssrc, &rtx_ssrc); - if (has_rtx && engine()->vie()->rtp()->SetLocalSSRC( - channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, stream_idx) != 0) { - LOG_RTCERR4(SetLocalSSRC, channel_id, rtx_ssrc, - webrtc::kViEStreamTypeRtx, stream_idx); +bool WebRtcVideoMediaChannel::SetPrimaryAndRtxSsrcs( + int channel_id, int idx, uint32 primary_ssrc, + const StreamParams& send_params) { + LOG(LS_INFO) << "Set primary ssrc " << primary_ssrc + << " on channel " << channel_id << " idx " << idx; + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx) != 0) { + LOG_RTCERR4(SetLocalSSRC, + channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx); return false; } + + uint32 rtx_ssrc = 0; + if (send_params.GetFidSsrc(primary_ssrc, &rtx_ssrc)) { + LOG(LS_INFO) << "Set rtx ssrc " << rtx_ssrc + << " on channel " << channel_id << " idx " << idx; + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx) != 0) { + LOG_RTCERR4(SetLocalSSRC, + channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx); + return false; + } + } + return true; +} + +bool WebRtcVideoMediaChannel::SetLimitedNumberOfSendSsrcs( + int channel_id, const StreamParams& sp, size_t limit) { + const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics); + if (!sim_group || limit == 1) { + return SetPrimaryAndRtxSsrcs(channel_id, 0, sp.first_ssrc(), sp); + } + + std::vector<uint32> ssrcs = sim_group->ssrcs; + for (size_t i = 0; i < ssrcs.size() && i < limit; ++i) { + if (!SetPrimaryAndRtxSsrcs(channel_id, static_cast<int>(i), ssrcs[i], sp)) { + return false; + } + } return true; } +bool WebRtcVideoMediaChannel::SetSendSsrcs( + int channel_id, const StreamParams& sp, + const webrtc::VideoCodec& codec) { + // TODO(pthatcher): Support more than one primary SSRC per stream. + return SetLimitedNumberOfSendSsrcs(channel_id, sp, 1); +} + void WebRtcVideoMediaChannel::MaybeConnectCapturer(VideoCapturer* capturer) { if (capturer && GetSendChannelNum(capturer) == 1) { capturer->SignalVideoFrame.connect(this, @@ -4184,6 +4206,18 @@ void WebRtcVideoMediaChannel::MaybeDisconnectCapturer(VideoCapturer* capturer) { } } +void WebRtcVideoMediaChannel::SetReceiverReportSsrc(uint32 ssrc) { + for (RecvChannelMap::const_iterator it = recv_channels_.begin(); + it != recv_channels_.end(); ++it) { + int channel_id = it->second->channel_id(); + if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, ssrc) != 0) { + LOG_RTCERR2(SetLocalSSRC, channel_id, ssrc); + ASSERT(false); + } + } + receiver_report_ssrc_ = ssrc; +} + } // namespace cricket #endif // HAVE_WEBRTC_VIDEO diff --git a/media/webrtc/webrtcvideoengine.h b/media/webrtc/webrtcvideoengine.h index c31f547..6f939d2 100644 --- a/media/webrtc/webrtcvideoengine.h +++ b/media/webrtc/webrtcvideoengine.h @@ -42,6 +42,9 @@ #if !defined(LIBPEERCONNECTION_LIB) && \ !defined(LIBPEERCONNECTION_IMPLEMENTATION) +// If you hit this, then you've tried to include this header from outside +// a shared library. An instance of this class must only be created from +// within the library that actually implements it. #error "Bogus include." #endif @@ -98,24 +101,23 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, ViEWrapper* vie_wrapper, ViETraceWrapper* tracing, rtc::CpuMonitor* cpu_monitor); - ~WebRtcVideoEngine(); + virtual ~WebRtcVideoEngine(); // Basic video engine implementation. bool Init(rtc::Thread* worker_thread); void Terminate(); int GetCapabilities(); - bool SetOptions(const VideoOptions &options); bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); VideoEncoderConfig GetDefaultEncoderConfig() const; - WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel); + virtual WebRtcVideoMediaChannel* CreateChannel( + VoiceMediaChannel* voice_channel); const std::vector<VideoCodec>& codecs() const; const std::vector<RtpHeaderExtension>& rtp_header_extensions() const; void SetLogging(int min_sev, const char* filter); - bool SetLocalRenderer(VideoRenderer* renderer); sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; // Set the VoiceEngine for A/V sync. This can only be called before Init. @@ -127,7 +129,8 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, // Set a WebRtcVideoEncoderFactory for external encoding. Video engine does // not take the ownership of |encoder_factory|. The caller needs to make sure // that |encoder_factory| outlives the video engine. - void SetExternalEncoderFactory(WebRtcVideoEncoderFactory* encoder_factory); + virtual void SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory); // Enable the render module with timing control. bool EnableTimedRender(); @@ -225,9 +228,6 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, VideoChannels channels_; bool capture_started_; - int local_renderer_w_; - int local_renderer_h_; - VideoRenderer* local_renderer_; rtc::scoped_ptr<rtc::CpuMonitor> cpu_monitor_; }; @@ -238,7 +238,7 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, public: WebRtcVideoMediaChannel(WebRtcVideoEngine* engine, VoiceMediaChannel* voice_channel); - ~WebRtcVideoMediaChannel(); + virtual ~WebRtcVideoMediaChannel(); bool Init(); WebRtcVideoEngine* engine() { return engine_; } @@ -310,6 +310,20 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, virtual int SendPacket(int channel, const void* data, int len); virtual int SendRTCPPacket(int channel, const void* data, int len); + // Checks the current bitrate estimate and modifies the bitrates + // accordingly, including converting kAutoBandwidth to the correct defaults. + virtual void SanitizeBitrates( + int channel_id, webrtc::VideoCodec* video_codec); + virtual void LogSendCodecChange(const std::string& reason); + bool SetPrimaryAndRtxSsrcs( + int channel_id, int idx, uint32 primary_ssrc, + const StreamParams& send_params); + bool SetLimitedNumberOfSendSsrcs( + int channel_id, const StreamParams& send_params, size_t limit); + virtual bool SetSendSsrcs( + int channel_id, const StreamParams& send_params, + const webrtc::VideoCodec& codec); + private: typedef std::map<uint32, WebRtcVideoChannelRecvInfo*> RecvChannelMap; typedef std::map<uint32, WebRtcVideoChannelSendInfo*> SendChannelMap; @@ -339,22 +353,23 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool SetSendCodec(const webrtc::VideoCodec& codec); bool SetSendCodec(WebRtcVideoChannelSendInfo* send_channel, const webrtc::VideoCodec& codec); - void LogSendCodecChange(const std::string& reason); // Prepares the channel with channel id |info->channel_id()| to receive all // codecs in |receive_codecs_| and start receive packets. bool SetReceiveCodecs(WebRtcVideoChannelRecvInfo* info); // Returns the channel ID that receives the stream with SSRC |ssrc|. int GetRecvChannelId(uint32 ssrc); bool MaybeSetRtxSsrc(const StreamParams& sp, int channel_id); + // Create and register an external endcoder if it's possible to do + // so and one isn't already registered. + bool MaybeRegisterExternalEncoder( + WebRtcVideoChannelSendInfo* send_channel, + const webrtc::VideoCodec& codec); // Given captured video frame size, checks if we need to reset vie send codec. // |reset| is set to whether resetting has happened on vie or not. // Returns false on error. bool MaybeResetVieSendCodec(WebRtcVideoChannelSendInfo* send_channel, int new_width, int new_height, bool is_screencast, bool* reset); - // Checks the current bitrate estimate and modifies the bitrates - // accordingly, including converting kAutoBandwidth to the correct defaults. - void MaybeChangeBitrates(int channel_id, webrtc::VideoCodec* video_codec); // Helper function for starting the sending of media on all channels or // |channel_id|. Note that these two function do not change |sending_|. bool StartSend(); @@ -371,9 +386,10 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, // Returns the ssrc key corresponding to the provided local SSRC in // |ssrc_key|. The return value is true upon success. If the local // ssrc correspond to that of the default channel the key is - // kDefaultChannelSsrcKey. - // For all other channels the returned ssrc key will be the same as - // the local ssrc. + // kDefaultChannelSsrcKey. For all other channels the returned ssrc + // key will be the same as the local ssrc. If a stream has more + // than one ssrc, the first (corresponding to + // StreamParams::first_ssrc()) is used as the key. bool GetSendChannelSsrcKey(uint32 local_ssrc, uint32* ssrc_key); WebRtcVideoChannelSendInfo* GetDefaultSendChannel(); WebRtcVideoChannelSendInfo* GetSendChannelBySsrcKey(uint32 ssrc_key); @@ -387,8 +403,6 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool IsDefaultChannelId(int channel_id) const { return channel_id == default_channel_id_; } - bool GetDefaultRenderer(VideoRenderer** renderer); - bool DeleteSendChannel(uint32 ssrc_key); WebRtcVideoChannelRecvInfo* GetDefaultRecvChannel(); @@ -414,10 +428,6 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, // Signal when cpu adaptation has no further scope to adapt. void OnCpuAdaptationUnable(); - // Set the local (send-side) RTX SSRC corresponding to primary_ssrc. - bool SetLocalRtxSsrc(int channel_id, const StreamParams& send_params, - uint32 primary_ssrc, int stream_idx); - // Connect |capturer| to WebRtcVideoMediaChannel if it is only registered // to one send channel, i.e. the first send channel. void MaybeConnectCapturer(VideoCapturer* capturer); @@ -427,6 +437,9 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool RemoveRecvStreamInternal(uint32 ssrc); + // Set the ssrc to use for RTCP receiver reports. + void SetReceiverReportSsrc(uint32 ssrc); + // Global state. WebRtcVideoEngine* engine_; VoiceMediaChannel* voice_channel_; @@ -454,6 +467,7 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, std::map<int, int> associated_payload_types_; bool render_started_; uint32 first_receive_ssrc_; + uint32 receiver_report_ssrc_; std::vector<RtpHeaderExtension> receive_extensions_; int num_unsignalled_recv_channels_; diff --git a/media/webrtc/webrtcvideoengine2.cc b/media/webrtc/webrtcvideoengine2.cc index ea53596..26e3079 100644 --- a/media/webrtc/webrtcvideoengine2.cc +++ b/media/webrtc/webrtcvideoengine2.cc @@ -42,8 +42,7 @@ #include "webrtc/base/logging.h" #include "webrtc/base/stringutils.h" #include "webrtc/call.h" -// TODO(pbos): Move codecs out of modules (webrtc:3070). -#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" +#include "webrtc/video_encoder.h" #define UNIMPLEMENTED \ LOG(LS_ERROR) << "Call to unimplemented function " << __FUNCTION__; \ @@ -55,6 +54,8 @@ namespace cricket { // duration hasn't been implemented. static const int kNackHistoryMs = 1000; +static const int kDefaultQpMax = 56; + static const int kDefaultRtcpReceiverReportSsrc = 1; struct VideoCodecPref { @@ -187,7 +188,7 @@ std::vector<webrtc::VideoStream> WebRtcVideoEncoderFactory2::CreateVideoStreams( stream.min_bitrate_bps = min_bitrate * 1000; stream.target_bitrate_bps = stream.max_bitrate_bps = max_bitrate * 1000; - int max_qp = 56; + int max_qp = kDefaultQpMax; codec.GetParam(kCodecParamMaxQuantization, &max_qp); stream.max_qp = max_qp; std::vector<webrtc::VideoStream> streams; @@ -200,7 +201,7 @@ webrtc::VideoEncoder* WebRtcVideoEncoderFactory2::CreateVideoEncoder( const VideoOptions& options) { assert(SupportsCodec(codec)); if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { - return webrtc::VP8Encoder::Create(); + return webrtc::VideoEncoder::Create(webrtc::VideoEncoder::kVp8); } // This shouldn't happen, we should be able to create encoders for all codecs // we support. @@ -213,14 +214,9 @@ void* WebRtcVideoEncoderFactory2::CreateVideoEncoderSettings( const VideoOptions& options) { assert(SupportsCodec(codec)); if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { - webrtc::VideoCodecVP8* settings = new webrtc::VideoCodecVP8(); - settings->resilience = webrtc::kResilientStream; - settings->numberOfTemporalLayers = 1; + webrtc::VideoCodecVP8* settings = new webrtc::VideoCodecVP8( + webrtc::VideoEncoder::GetDefaultVp8Settings()); options.video_noise_reduction.Get(&settings->denoisingOn); - settings->errorConcealmentOn = false; - settings->automaticResizeOn = false; - settings->frameDroppingOn = true; - settings->keyFrameInterval = 3000; return settings; } return NULL; @@ -233,13 +229,9 @@ void WebRtcVideoEncoderFactory2::DestroyVideoEncoderSettings( if (encoder_settings == NULL) { return; } - if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { delete reinterpret_cast<webrtc::VideoCodecVP8*>(encoder_settings); - return; } - // We should be able to destroy all encoder settings we've allocated. - assert(false); } bool WebRtcVideoEncoderFactory2::SupportsCodec(const VideoCodec& codec) { @@ -283,37 +275,19 @@ void DefaultUnsignalledSsrcHandler::SetDefaultRenderer( } WebRtcVideoEngine2::WebRtcVideoEngine2() - : default_codec_format_(kDefaultVideoCodecPref.width, - kDefaultVideoCodecPref.height, - FPS_TO_INTERVAL(kDefaultFramerate), - FOURCC_ANY) { - // Construct without a factory or voice engine. - Construct(NULL, NULL, new rtc::CpuMonitor(NULL)); -} - -WebRtcVideoEngine2::WebRtcVideoEngine2( - WebRtcVideoChannelFactory* channel_factory) - : default_codec_format_(kDefaultVideoCodecPref.width, + : worker_thread_(NULL), + voice_engine_(NULL), + video_codecs_(DefaultVideoCodecs()), + default_codec_format_(kDefaultVideoCodecPref.width, kDefaultVideoCodecPref.height, FPS_TO_INTERVAL(kDefaultFramerate), - FOURCC_ANY) { - // Construct without a voice engine. - Construct(channel_factory, NULL, new rtc::CpuMonitor(NULL)); -} - -void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, - WebRtcVoiceEngine* voice_engine, - rtc::CpuMonitor* cpu_monitor) { - LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2"; - worker_thread_ = NULL; - voice_engine_ = voice_engine; - initialized_ = false; - capture_started_ = false; - cpu_monitor_.reset(cpu_monitor); - channel_factory_ = channel_factory; - - video_codecs_ = DefaultVideoCodecs(); - + FOURCC_ANY), + initialized_(false), + cpu_monitor_(new rtc::CpuMonitor(NULL)), + channel_factory_(NULL), + external_decoder_factory_(NULL), + external_encoder_factory_(NULL) { + LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2()"; rtp_header_extensions_.push_back( RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension, kRtpTimestampOffsetHeaderExtensionDefaultId)); @@ -322,6 +296,11 @@ void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, kRtpAbsoluteSenderTimeHeaderExtensionDefaultId)); } +void WebRtcVideoEngine2::SetChannelFactory( + WebRtcVideoChannelFactory* channel_factory) { + channel_factory_ = channel_factory; +} + WebRtcVideoEngine2::~WebRtcVideoEngine2() { LOG(LS_INFO) << "WebRtcVideoEngine2::~WebRtcVideoEngine2"; @@ -355,14 +334,6 @@ void WebRtcVideoEngine2::Terminate() { int WebRtcVideoEngine2::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } -bool WebRtcVideoEngine2::SetOptions(const VideoOptions& options) { - // TODO(pbos): Do we need this? This is a no-op in the existing - // WebRtcVideoEngine implementation. - LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); - // options_ = options; - return true; -} - bool WebRtcVideoEngine2::SetDefaultEncoderConfig( const VideoEncoderConfig& config) { const VideoCodec& codec = config.max_codec; @@ -424,14 +395,32 @@ void WebRtcVideoEngine2::SetLogging(int min_sev, const char* filter) { } } -bool WebRtcVideoEngine2::EnableTimedRender() { - // TODO(pbos): Figure out whether this can be removed. - return true; +void WebRtcVideoEngine2::SetExternalDecoderFactory( + WebRtcVideoDecoderFactory* decoder_factory) { + external_decoder_factory_ = decoder_factory; } -bool WebRtcVideoEngine2::SetLocalRenderer(VideoRenderer* renderer) { - // TODO(pbos): Implement or remove. Unclear which stream should be rendered - // locally even. +void WebRtcVideoEngine2::SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory) { + if (external_encoder_factory_ == encoder_factory) { + return; + } + if (external_encoder_factory_) { + external_encoder_factory_->RemoveObserver(this); + } + external_encoder_factory_ = encoder_factory; + if (external_encoder_factory_) { + external_encoder_factory_->AddObserver(this); + } + + // Invoke OnCodecAvailable() here in case the list of codecs is already + // available when the encoder factory is installed. If not the encoder + // factory will invoke the callback later when the codecs become available. + OnCodecsAvailable(); +} + +bool WebRtcVideoEngine2::EnableTimedRender() { + // TODO(pbos): Figure out whether this can be removed. return true; } @@ -517,6 +506,9 @@ WebRtcVideoEncoderFactory2* WebRtcVideoEngine2::GetVideoEncoderFactory() { return &default_video_encoder_factory_; } +void WebRtcVideoEngine2::OnCodecsAvailable() { + // TODO(pbos): Implement. +} // Thin map between VideoFrame and an existing webrtc::I420VideoFrame // to avoid having to copy the rendered VideoFrame prematurely. // This implementation is only safe to use in a const context and should never @@ -679,8 +671,8 @@ WebRtcVideoChannel2::WebRtcVideoChannel2( WebRtcVideoEngine2* engine, VoiceMediaChannel* voice_channel, WebRtcVideoEncoderFactory2* encoder_factory) - : encoder_factory_(encoder_factory), - unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_) { + : unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_), + encoder_factory_(encoder_factory) { // TODO(pbos): Connect the video and audio with |voice_channel|. webrtc::Call::Config config(this); Construct(webrtc::Call::Create(config), engine); @@ -690,8 +682,8 @@ WebRtcVideoChannel2::WebRtcVideoChannel2( webrtc::Call* call, WebRtcVideoEngine2* engine, WebRtcVideoEncoderFactory2* encoder_factory) - : encoder_factory_(encoder_factory), - unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_) { + : unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_), + encoder_factory_(encoder_factory) { Construct(call, engine); } @@ -1174,7 +1166,9 @@ void WebRtcVideoChannel2::OnRtcpReceived( } void WebRtcVideoChannel2::OnReadyToSend(bool ready) { - LOG(LS_VERBOSE) << "OnReadySend: " << (ready ? "Ready." : "Not ready."); + LOG(LS_VERBOSE) << "OnReadyToSend: " << (ready ? "Ready." : "Not ready."); + call_->SignalNetworkState(ready ? webrtc::Call::kNetworkUp + : webrtc::Call::kNetworkDown); } bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) { @@ -1315,10 +1309,10 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::WebRtcVideoSendStream( const StreamParams& sp, const std::vector<webrtc::RtpExtension>& rtp_extensions) : call_(call), - parameters_(webrtc::VideoSendStream::Config(), options, codec_settings), encoder_factory_(encoder_factory), - capturer_(NULL), stream_(NULL), + parameters_(webrtc::VideoSendStream::Config(), options, codec_settings), + capturer_(NULL), sending_(false), muted_(false) { parameters_.config.rtp.max_packet_size = kVideoMtu; @@ -1385,16 +1379,10 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( const VideoFrame* frame) { LOG(LS_VERBOSE) << "InputFrame: " << frame->GetWidth() << "x" << frame->GetHeight(); - bool is_screencast = capturer->IsScreencast(); // Lock before copying, can be called concurrently when swapping input source. rtc::CritScope frame_cs(&frame_lock_); - if (!muted_) { - ConvertToI420VideoFrame(*frame, &video_frame_); - } else { - // Create a tiny black frame to transmit instead. - CreateBlackFrame(&video_frame_, 1, 1); - is_screencast = false; - } + ConvertToI420VideoFrame(*frame, &video_frame_); + rtc::CritScope cs(&lock_); if (stream_ == NULL) { LOG(LS_WARNING) << "Capturer inputting frames before send codecs are " @@ -1406,14 +1394,20 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( LOG(LS_VERBOSE) << "VideoFormat 0x0 set, Dropping frame."; return; } - // Reconfigure codec if necessary. - if (is_screencast) { - SetDimensions(video_frame_.width(), video_frame_.height()); + if (muted_) { + // Create a black frame to transmit instead. + CreateBlackFrame(&video_frame_, + static_cast<int>(frame->GetWidth()), + static_cast<int>(frame->GetHeight())); } + // Reconfigure codec if necessary. + SetDimensions( + video_frame_.width(), video_frame_.height(), capturer->IsScreencast()); + LOG(LS_VERBOSE) << "SwapFrame: " << video_frame_.width() << "x" << video_frame_.height() << " -> (codec) " - << parameters_.video_streams.back().width << "x" - << parameters_.video_streams.back().height; + << parameters_.encoder_config.streams.back().width << "x" + << parameters_.encoder_config.streams.back().height; stream_->Input()->SwapFrame(&video_frame_); } @@ -1437,7 +1431,7 @@ bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetCapturer( black_frame.CreateEmptyFrame( width, height, width, half_width, half_width); SetWebRtcFrameToBlack(&black_frame); - SetDimensions(width, height); + SetDimensions(width, height, false); stream_->Input()->SwapFrame(&black_frame); } @@ -1469,9 +1463,9 @@ bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetVideoFormat( << parameters_.config.rtp.ssrcs[0] << "."; } else { // TODO(pbos): Fix me, this only affects the last stream! - parameters_.video_streams.back().max_framerate = + parameters_.encoder_config.streams.back().max_framerate = VideoFormat::IntervalToFps(format.interval); - SetDimensions(format.width, format.height); + SetDimensions(format.width, format.height, false); } format_ = format; @@ -1517,7 +1511,7 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::SetCodecAndOptions( if (video_streams.empty()) { return; } - parameters_.video_streams = video_streams; + parameters_.encoder_config.streams = video_streams; format_ = VideoFormat(codec_settings.codec.width, codec_settings.codec.height, VideoFormat::FpsToInterval(30), @@ -1560,35 +1554,55 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::SetRtpExtensions( RecreateWebRtcStream(); } -void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions(int width, - int height) { - assert(!parameters_.video_streams.empty()); +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions( + int width, + int height, + bool override_max) { + assert(!parameters_.encoder_config.streams.empty()); LOG(LS_VERBOSE) << "SetDimensions: " << width << "x" << height; - if (parameters_.video_streams.back().width == width && - parameters_.video_streams.back().height == height) { + + VideoCodecSettings codec_settings; + parameters_.codec_settings.Get(&codec_settings); + // Restrict dimensions according to codec max. + if (!override_max) { + if (codec_settings.codec.width < width) + width = codec_settings.codec.width; + if (codec_settings.codec.height < height) + height = codec_settings.codec.height; + } + + if (parameters_.encoder_config.streams.back().width == width && + parameters_.encoder_config.streams.back().height == height) { return; } - // TODO(pbos): Fix me, this only affects the last stream! - parameters_.video_streams.back().width = width; - parameters_.video_streams.back().height = height; + webrtc::VideoEncoderConfig encoder_config = parameters_.encoder_config; + encoder_config.encoder_specific_settings = + encoder_factory_->CreateVideoEncoderSettings(codec_settings.codec, + parameters_.options); - VideoCodecSettings codec_settings; - parameters_.codec_settings.Get(&codec_settings); - void* encoder_settings = encoder_factory_->CreateVideoEncoderSettings( - codec_settings.codec, parameters_.options); + VideoCodec codec = codec_settings.codec; + codec.width = width; + codec.height = height; + + encoder_config.streams = encoder_factory_->CreateVideoStreams( + codec, parameters_.options, parameters_.config.rtp.ssrcs.size()); - bool stream_reconfigured = stream_->ReconfigureVideoEncoder( - parameters_.video_streams, encoder_settings); + bool stream_reconfigured = stream_->ReconfigureVideoEncoder(encoder_config); - encoder_factory_->DestroyVideoEncoderSettings(codec_settings.codec, - encoder_settings); + encoder_factory_->DestroyVideoEncoderSettings( + codec_settings.codec, + encoder_config.encoder_specific_settings); + + encoder_config.encoder_specific_settings = NULL; if (!stream_reconfigured) { LOG(LS_WARNING) << "Failed to reconfigure video encoder for dimensions: " << width << "x" << height; return; } + + parameters_.encoder_config = encoder_config; } void WebRtcVideoChannel2::WebRtcVideoSendStream::Start() { @@ -1652,9 +1666,9 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::GetVideoSenderInfo() { info.input_frame_width = last_captured_frame_format.width; info.input_frame_height = last_captured_frame_format.height; info.send_frame_width = - static_cast<int>(parameters_.video_streams.front().width); + static_cast<int>(parameters_.encoder_config.streams.front().width); info.send_frame_height = - static_cast<int>(parameters_.video_streams.front().height); + static_cast<int>(parameters_.encoder_config.streams.front().height); } // TODO(pbos): Support or remove the following stats. @@ -1671,14 +1685,18 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::RecreateWebRtcStream() { VideoCodecSettings codec_settings; parameters_.codec_settings.Get(&codec_settings); - void* encoder_settings = encoder_factory_->CreateVideoEncoderSettings( - codec_settings.codec, parameters_.options); + parameters_.encoder_config.encoder_specific_settings = + encoder_factory_->CreateVideoEncoderSettings(codec_settings.codec, + parameters_.options); - stream_ = call_->CreateVideoSendStream( - parameters_.config, parameters_.video_streams, encoder_settings); + stream_ = call_->CreateVideoSendStream(parameters_.config, + parameters_.encoder_config); - encoder_factory_->DestroyVideoEncoderSettings(codec_settings.codec, - encoder_settings); + encoder_factory_->DestroyVideoEncoderSettings( + codec_settings.codec, + parameters_.encoder_config.encoder_specific_settings); + + parameters_.encoder_config.encoder_specific_settings = NULL; if (sending_) { stream_->Start(); @@ -1690,11 +1708,11 @@ WebRtcVideoChannel2::WebRtcVideoReceiveStream::WebRtcVideoReceiveStream( const webrtc::VideoReceiveStream::Config& config, const std::vector<VideoCodecSettings>& recv_codecs) : call_(call), - config_(config), stream_(NULL), + config_(config), + renderer_(NULL), last_width_(-1), - last_height_(-1), - renderer_(NULL) { + last_height_(-1) { config_.renderer = this; // SetRecvCodecs will also reset (start) the VideoReceiveStream. SetRecvCodecs(recv_codecs); diff --git a/media/webrtc/webrtcvideoengine2.h b/media/webrtc/webrtcvideoengine2.h index c7eb6a4..18a80d7 100644 --- a/media/webrtc/webrtcvideoengine2.h +++ b/media/webrtc/webrtcvideoengine2.h @@ -34,10 +34,12 @@ #include "talk/media/base/mediaengine.h" #include "talk/media/webrtc/webrtcvideochannelfactory.h" +#include "talk/media/webrtc/webrtcvideodecoderfactory.h" +#include "talk/media/webrtc/webrtcvideoencoderfactory.h" #include "webrtc/base/cpumonitor.h" #include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_annotations.h" #include "webrtc/common_video/interface/i420_video_frame.h" -#include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/transport.h" #include "webrtc/video_receive_stream.h" #include "webrtc/video_renderer.h" @@ -65,14 +67,13 @@ class VideoFrame; class VideoProcessor; class VideoRenderer; class VoiceMediaChannel; -class WebRtcVideoChannel2; class WebRtcDecoderObserver; class WebRtcEncoderObserver; class WebRtcLocalStreamInfo; class WebRtcRenderAdapter; +class WebRtcVideoChannel2; class WebRtcVideoChannelRecvInfo; class WebRtcVideoChannelSendInfo; -class WebRtcVideoDecoderFactory; class WebRtcVoiceEngine; struct CapturedFrame; @@ -119,9 +120,8 @@ class WebRtcVideoEncoderFactory2 { const VideoCodec& codec, const VideoOptions& options); - virtual void* CreateVideoEncoderSettings( - const VideoCodec& codec, - const VideoOptions& options); + virtual void* CreateVideoEncoderSettings(const VideoCodec& codec, + const VideoOptions& options); virtual void DestroyVideoEncoderSettings(const VideoCodec& codec, void* encoder_settings); @@ -130,20 +130,21 @@ class WebRtcVideoEncoderFactory2 { }; // WebRtcVideoEngine2 is used for the new native WebRTC Video API (webrtc:1667). -class WebRtcVideoEngine2 : public sigslot::has_slots<> { +class WebRtcVideoEngine2 : public sigslot::has_slots<>, + public WebRtcVideoEncoderFactory::Observer { public: // Creates the WebRtcVideoEngine2 with internal VideoCaptureModule. WebRtcVideoEngine2(); - // Custom WebRtcVideoChannelFactory for testing purposes. - explicit WebRtcVideoEngine2(WebRtcVideoChannelFactory* channel_factory); - ~WebRtcVideoEngine2(); + virtual ~WebRtcVideoEngine2(); + + // Use a custom WebRtcVideoChannelFactory (for testing purposes). + void SetChannelFactory(WebRtcVideoChannelFactory* channel_factory); // Basic video engine implementation. bool Init(rtc::Thread* worker_thread); void Terminate(); int GetCapabilities(); - bool SetOptions(const VideoOptions& options); bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); VideoEncoderConfig GetDefaultEncoderConfig() const; @@ -153,20 +154,23 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { const std::vector<RtpHeaderExtension>& rtp_header_extensions() const; void SetLogging(int min_sev, const char* filter); + // Set a WebRtcVideoDecoderFactory for external decoding. Video engine does + // not take the ownership of |decoder_factory|. The caller needs to make sure + // that |decoder_factory| outlives the video engine. + void SetExternalDecoderFactory(WebRtcVideoDecoderFactory* decoder_factory); + // Set a WebRtcVideoEncoderFactory for external encoding. Video engine does + // not take the ownership of |encoder_factory|. The caller needs to make sure + // that |encoder_factory| outlives the video engine. + virtual void SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory); + bool EnableTimedRender(); - // No-op, never used. - bool SetLocalRenderer(VideoRenderer* renderer); // This is currently ignored. sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; // Set the VoiceEngine for A/V sync. This can only be called before Init. bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine); - // Functions called by WebRtcVideoChannel2. - const VideoFormat& default_codec_format() const { - return default_codec_format_; - } - bool FindCodec(const VideoCodec& in); bool CanSendCodec(const VideoCodec& in, const VideoCodec& current, @@ -181,9 +185,7 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { virtual WebRtcVideoEncoderFactory2* GetVideoEncoderFactory(); private: - void Construct(WebRtcVideoChannelFactory* channel_factory, - WebRtcVoiceEngine* voice_engine, - rtc::CpuMonitor* cpu_monitor); + virtual void OnCodecsAvailable() OVERRIDE; rtc::Thread* worker_thread_; WebRtcVoiceEngine* voice_engine_; @@ -193,8 +195,6 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { bool initialized_; - bool capture_started_; - // Critical section to protect the media processor register/unregister // while processing a frame rtc::CriticalSection signal_media_critical_; @@ -202,6 +202,9 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { rtc::scoped_ptr<rtc::CpuMonitor> cpu_monitor_; WebRtcVideoChannelFactory* channel_factory_; WebRtcVideoEncoderFactory2 default_video_encoder_factory_; + + WebRtcVideoDecoderFactory* external_decoder_factory_; + WebRtcVideoEncoderFactory* external_encoder_factory_; }; class WebRtcVideoChannel2 : public rtc::MessageHandler, @@ -328,13 +331,16 @@ class WebRtcVideoChannel2 : public rtc::MessageHandler, // Sent resolutions + bitrates etc. by the underlying VideoSendStream, // typically changes when setting a new resolution or reconfiguring // bitrates. - std::vector<webrtc::VideoStream> video_streams; + webrtc::VideoEncoderConfig encoder_config; }; void SetCodecAndOptions(const VideoCodecSettings& codec, - const VideoOptions& options); - void RecreateWebRtcStream(); - void SetDimensions(int width, int height); + const VideoOptions& options) + EXCLUSIVE_LOCKS_REQUIRED(lock_); + void RecreateWebRtcStream() EXCLUSIVE_LOCKS_REQUIRED(lock_); + // When |override_max| is false constrain width/height to codec dimensions. + void SetDimensions(int width, int height, bool override_max) + EXCLUSIVE_LOCKS_REQUIRED(lock_); webrtc::Call* const call_; WebRtcVideoEncoderFactory2* const encoder_factory_; diff --git a/media/webrtc/webrtcvideoengine2_unittest.cc b/media/webrtc/webrtcvideoengine2_unittest.cc index 9c313e6..6112c50 100644 --- a/media/webrtc/webrtcvideoengine2_unittest.cc +++ b/media/webrtc/webrtcvideoengine2_unittest.cc @@ -68,13 +68,10 @@ void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec) { namespace cricket { FakeVideoSendStream::FakeVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) - : sending_(false), - config_(config), - codec_settings_set_(false) { + const webrtc::VideoEncoderConfig& encoder_config) + : sending_(false), config_(config), codec_settings_set_(false) { assert(config.encoder_settings.encoder != NULL); - ReconfigureVideoEncoder(video_streams, encoder_settings); + ReconfigureVideoEncoder(encoder_config); } webrtc::VideoSendStream::Config FakeVideoSendStream::GetConfig() { @@ -82,7 +79,7 @@ webrtc::VideoSendStream::Config FakeVideoSendStream::GetConfig() { } std::vector<webrtc::VideoStream> FakeVideoSendStream::GetVideoStreams() { - return video_streams_; + return encoder_config_.streams; } bool FakeVideoSendStream::IsSending() const { @@ -104,15 +101,14 @@ webrtc::VideoSendStream::Stats FakeVideoSendStream::GetStats() const { } bool FakeVideoSendStream::ReconfigureVideoEncoder( - const std::vector<webrtc::VideoStream>& streams, - const void* encoder_specific) { - video_streams_ = streams; - if (encoder_specific != NULL) { + const webrtc::VideoEncoderConfig& config) { + encoder_config_ = config; + if (config.encoder_specific_settings != NULL) { assert(config_.encoder_settings.payload_name == "VP8"); - vp8_settings_ = - *reinterpret_cast<const webrtc::VideoCodecVP8*>(encoder_specific); + vp8_settings_ = *reinterpret_cast<const webrtc::VideoCodecVP8*>( + config.encoder_specific_settings); } - codec_settings_set_ = encoder_specific != NULL; + codec_settings_set_ = config.encoder_specific_settings != NULL; return true; } @@ -157,7 +153,9 @@ void FakeVideoReceiveStream::Stop() { void FakeVideoReceiveStream::GetCurrentReceiveCodec(webrtc::VideoCodec* codec) { } -FakeCall::FakeCall() { SetVideoCodecs(GetDefaultVideoCodecs()); } +FakeCall::FakeCall() : network_state_(kNetworkUp) { + SetVideoCodecs(GetDefaultVideoCodecs()); +} FakeCall::~FakeCall() { EXPECT_EQ(0u, video_send_streams_.size()); @@ -218,12 +216,15 @@ std::vector<webrtc::VideoCodec> FakeCall::GetDefaultVideoCodecs() { return codecs; } +webrtc::Call::NetworkState FakeCall::GetNetworkState() const { + return network_state_; +} + webrtc::VideoSendStream* FakeCall::CreateVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) { + const webrtc::VideoEncoderConfig& encoder_config) { FakeVideoSendStream* fake_stream = - new FakeVideoSendStream(config, video_streams, encoder_settings); + new FakeVideoSendStream(config, encoder_config); video_send_streams_.push_back(fake_stream); return fake_stream; } @@ -274,6 +275,10 @@ uint32_t FakeCall::ReceiveBitrateEstimate() { return 0; } +void FakeCall::SignalNetworkState(webrtc::Call::NetworkState state) { + network_state_ = state; +} + FakeWebRtcVideoChannel2::FakeWebRtcVideoChannel2( FakeCall* call, WebRtcVideoEngine2* engine, @@ -289,6 +294,7 @@ FakeWebRtcVideoChannel2::~FakeWebRtcVideoChannel2() { VoiceMediaChannel* FakeWebRtcVideoChannel2::GetVoiceChannel() { return voice_channel_; } + FakeCall* FakeWebRtcVideoChannel2::GetFakeCall() { return fake_call_; } @@ -309,7 +315,8 @@ WebRtcVideoChannel2* FakeWebRtcVideoMediaChannelFactory::Create( class WebRtcVideoEngine2Test : public testing::Test { public: - WebRtcVideoEngine2Test() : engine_(&factory_) { + WebRtcVideoEngine2Test() { + engine_.SetChannelFactory(&factory_); std::vector<VideoCodec> engine_codecs = engine_.codecs(); assert(!engine_codecs.empty()); bool codec_set = false; @@ -535,15 +542,17 @@ WEBRTC_BASE_TEST(AdaptResolution16x10); WEBRTC_BASE_TEST(AdaptResolution4x3); -WEBRTC_BASE_TEST(MuteStream); - -WEBRTC_BASE_TEST(MultipleSendStreams); - // TODO(juberti): Restore this test once we support sending 0 fps. WEBRTC_DISABLED_BASE_TEST(AdaptDropAllFrames); // TODO(juberti): Understand why we get decode errors on this test. WEBRTC_DISABLED_BASE_TEST(AdaptFramerate); +WEBRTC_BASE_TEST(SendsLowerResolutionOnSmallerFrames); + +WEBRTC_BASE_TEST(MuteStream); + +WEBRTC_BASE_TEST(MultipleSendStreams); + WEBRTC_BASE_TEST(SetSendStreamFormat0x0); // TODO(zhurunz): Fix the flakey test. @@ -1611,8 +1620,17 @@ TEST_F(WebRtcVideoChannel2Test, DISABLED_UpdateEncoderCodecsAfterSetFactory) { FAIL() << "Not implemented."; // TODO(pbos): Implement. } -TEST_F(WebRtcVideoChannel2Test, DISABLED_OnReadyToSend) { - FAIL() << "Not implemented."; // TODO(pbos): Implement. +TEST_F(WebRtcVideoChannel2Test, OnReadyToSendSignalsNetworkState) { + EXPECT_EQ(webrtc::Call::kNetworkUp, + fake_channel_->GetFakeCall()->GetNetworkState()); + + channel_->OnReadyToSend(false); + EXPECT_EQ(webrtc::Call::kNetworkDown, + fake_channel_->GetFakeCall()->GetNetworkState()); + + channel_->OnReadyToSend(true); + EXPECT_EQ(webrtc::Call::kNetworkUp, + fake_channel_->GetFakeCall()->GetNetworkState()); } TEST_F(WebRtcVideoChannel2Test, DISABLED_CaptureFrameTimestampToNtpTimestamp) { diff --git a/media/webrtc/webrtcvideoengine2_unittest.h b/media/webrtc/webrtcvideoengine2_unittest.h index 54e6f06..30f1efb 100644 --- a/media/webrtc/webrtcvideoengine2_unittest.h +++ b/media/webrtc/webrtcvideoengine2_unittest.h @@ -39,8 +39,7 @@ namespace cricket { class FakeVideoSendStream : public webrtc::VideoSendStream { public: FakeVideoSendStream(const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings); + const webrtc::VideoEncoderConfig& encoder_config); webrtc::VideoSendStream::Config GetConfig(); std::vector<webrtc::VideoStream> GetVideoStreams(); @@ -51,8 +50,7 @@ class FakeVideoSendStream : public webrtc::VideoSendStream { virtual webrtc::VideoSendStream::Stats GetStats() const OVERRIDE; virtual bool ReconfigureVideoEncoder( - const std::vector<webrtc::VideoStream>& streams, - const void* encoder_specific); + const webrtc::VideoEncoderConfig& config) OVERRIDE; virtual webrtc::VideoSendStreamInput* Input() OVERRIDE; @@ -61,7 +59,7 @@ class FakeVideoSendStream : public webrtc::VideoSendStream { bool sending_; webrtc::VideoSendStream::Config config_; - std::vector<webrtc::VideoStream> video_streams_; + webrtc::VideoEncoderConfig encoder_config_; bool codec_settings_set_; webrtc::VideoCodecVP8 vp8_settings_; }; @@ -103,11 +101,12 @@ class FakeCall : public webrtc::Call { std::vector<webrtc::VideoCodec> GetDefaultVideoCodecs(); + webrtc::Call::NetworkState GetNetworkState() const; + private: virtual webrtc::VideoSendStream* CreateVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) OVERRIDE; + const webrtc::VideoEncoderConfig& encoder_config) OVERRIDE; virtual void DestroyVideoSendStream( webrtc::VideoSendStream* send_stream) OVERRIDE; @@ -122,6 +121,9 @@ class FakeCall : public webrtc::Call { virtual uint32_t SendBitrateEstimate() OVERRIDE; virtual uint32_t ReceiveBitrateEstimate() OVERRIDE; + virtual void SignalNetworkState(webrtc::Call::NetworkState state) OVERRIDE; + + webrtc::Call::NetworkState network_state_; std::vector<webrtc::VideoCodec> codecs_; std::vector<FakeVideoSendStream*> video_send_streams_; std::vector<FakeVideoReceiveStream*> video_receive_streams_; diff --git a/media/webrtc/webrtcvideoengine_unittest.cc b/media/webrtc/webrtcvideoengine_unittest.cc index 11edd05..35f05e1 100644 --- a/media/webrtc/webrtcvideoengine_unittest.cc +++ b/media/webrtc/webrtcvideoengine_unittest.cc @@ -26,22 +26,16 @@ */ #include "talk/media/base/constants.h" -#include "talk/media/base/fakemediaprocessor.h" #include "talk/media/base/fakenetworkinterface.h" -#include "talk/media/base/fakevideorenderer.h" #include "talk/media/base/mediachannel.h" #include "talk/media/base/testutils.h" -#include "talk/media/base/videoadapter.h" #include "talk/media/base/videoengine_unittest.h" -#include "talk/media/webrtc/fakewebrtcvideocapturemodule.h" #include "talk/media/webrtc/fakewebrtcvideoengine.h" -#include "talk/media/webrtc/fakewebrtcvoiceengine.h" #include "webrtc/base/fakecpumonitor.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/stream.h" -#include "talk/media/webrtc/webrtcvideocapturer.h" #include "talk/media/webrtc/webrtcvideoengine.h" #include "talk/media/webrtc/webrtcvideoframe.h" #include "talk/media/webrtc/webrtcvoiceengine.h" @@ -54,9 +48,6 @@ using cricket::kRtpTimestampOffsetHeaderExtension; using cricket::kRtpAbsoluteSenderTimeHeaderExtension; static const cricket::VideoCodec kVP8Codec720p(100, "VP8", 1280, 720, 30, 0); -static const cricket::VideoCodec kVP8Codec360p(100, "VP8", 640, 360, 30, 0); -static const cricket::VideoCodec kVP8Codec270p(100, "VP8", 480, 270, 30, 0); -static const cricket::VideoCodec kVP8Codec180p(100, "VP8", 320, 180, 30, 0); static const cricket::VideoCodec kVP8Codec(100, "VP8", 640, 400, 30, 0); static const cricket::VideoCodec kH264Codec(127, "H264", 640, 400, 30, 0); @@ -78,7 +69,6 @@ static const uint32 kSsrcs3[] = {1, 2, 3}; static const uint32 kRtxSsrcs1[] = {4}; static const uint32 kRtxSsrcs3[] = {4, 5, 6}; - class FakeViEWrapper : public cricket::ViEWrapper { public: explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine) @@ -1556,7 +1546,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetBandwidthInConference) { 768, kMinBandwidthKbps, kStartBandwidthKbps); } - // Test that sending screencast frames doesn't change bitrate. TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) { EXPECT_TRUE(SetupEngine()); @@ -1576,7 +1565,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) { VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0, 111); } - // Test SetSendSsrc. TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) { EXPECT_TRUE(SetupEngine()); @@ -1597,7 +1585,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) { EXPECT_STREQ("cname", rtcp_cname); } - // Test that the local SSRC is the same on sending and receiving channels if the // receive channel is created before the send channel. TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) { @@ -1618,7 +1605,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) { EXPECT_EQ(1, vie_.GetNumSsrcs(receive_channel_num)); } - // Test SetOptions with denoising flag. TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithDenoising) { EXPECT_TRUE(SetupEngine()); @@ -1707,7 +1693,6 @@ TEST_F(WebRtcVideoEngineTestFake, MultipleSendStreamsWithOneCapturer) { EXPECT_EQ(-1, vie_.GetIncomingFrameNum(channel1)); } - TEST_F(WebRtcVideoEngineTestFake, SendReceiveBitratesStats) { EXPECT_TRUE(SetupEngine()); cricket::VideoOptions options; @@ -1839,7 +1824,6 @@ TEST_F(WebRtcVideoEngineTestFake, TestSetInvalidCpuThreshold) { EXPECT_EQ(high, 1.0f); } - TEST_F(WebRtcVideoEngineTestFake, ResetCodecOnScreencast) { EXPECT_TRUE(SetupEngine()); cricket::VideoOptions options; @@ -1870,7 +1854,6 @@ TEST_F(WebRtcVideoEngineTestFake, ResetCodecOnScreencast) { EXPECT_FALSE(gcodec.codecSpecific.VP8.denoisingOn); } - TEST_F(WebRtcVideoEngineTestFake, DontRegisterDecoderIfFactoryIsNotGiven) { engine_.SetExternalDecoderFactory(NULL); EXPECT_TRUE(SetupEngine()); @@ -2397,9 +2380,6 @@ TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsUnsupportedCodec) { EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); } -// Disable for TSan v2, see -// https://code.google.com/p/webrtc/issues/detail?id=3671 for details. -#if !defined(THREAD_SANITIZER) TEST_F(WebRtcVideoMediaChannelTest, GetRtpSendTimeExtension) { // Enable RTP timestamp extension. const int id = 12; @@ -2411,7 +2391,6 @@ TEST_F(WebRtcVideoMediaChannelTest, GetRtpSendTimeExtension) { EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); EXPECT_EQ(id, channel_->GetRtpSendTimeExtnId()); } -#endif // if !defined(THREAD_SANITIZER) TEST_F(WebRtcVideoMediaChannelTest, SetSend) { Base::SetSend(); @@ -2574,7 +2553,6 @@ TEST_F(WebRtcVideoMediaChannelTest, RejectEmptyStreamParams) { Base::RejectEmptyStreamParams(); } - TEST_F(WebRtcVideoMediaChannelTest, AdaptResolution16x10) { Base::AdaptResolution16x10(); } diff --git a/media/webrtc/webrtcvideoframe_unittest.cc b/media/webrtc/webrtcvideoframe_unittest.cc index 4cfe7c0..5f65c58 100644 --- a/media/webrtc/webrtcvideoframe_unittest.cc +++ b/media/webrtc/webrtcvideoframe_unittest.cc @@ -27,14 +27,10 @@ #include "talk/media/base/videoframe_unittest.h" #include "talk/media/webrtc/webrtcvideoframe.h" -#include "webrtc/base/flags.h" - -extern int FLAG_yuvconverter_repeat; // From lmivideoframe_unittest.cc. class WebRtcVideoFrameTest : public VideoFrameTest<cricket::WebRtcVideoFrame> { public: WebRtcVideoFrameTest() { - repeat_ = FLAG_yuvconverter_repeat; } void TestInit(int cropped_width, int cropped_height) { @@ -136,7 +132,7 @@ TEST_WEBRTCVIDEOFRAME(ConstructI420CropVertical) TEST_WEBRTCVIDEOFRAME(ConstructBlack) // TODO(fbarchard): Implement Jpeg // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420) -// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422) +TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI411) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400) diff --git a/media/webrtc/webrtcvoiceengine.cc b/media/webrtc/webrtcvoiceengine.cc index 5cf53e4..a524bad 100644 --- a/media/webrtc/webrtcvoiceengine.cc +++ b/media/webrtc/webrtcvoiceengine.cc @@ -52,6 +52,7 @@ #include "webrtc/base/stringutils.h" #include "webrtc/common.h" #include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/video_engine/include/vie_network.h" #ifdef WIN32 #include <objbase.h> // NOLINT @@ -119,6 +120,7 @@ static const int kOpusStereoBitrate = 64000; // Opus bitrate should be in the range between 6000 and 510000. static const int kOpusMinBitrate = 6000; static const int kOpusMaxBitrate = 510000; + // Default audio dscp value. // See http://tools.ietf.org/html/rfc2474 for details. // See also http://tools.ietf.org/html/draft-jennings-rtcweb-qos-00 @@ -403,6 +405,7 @@ static bool IsOpusStereoEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamStereo, &value) && value == 1; } +// TODO(minyue): Clamp bitrate when invalid. static bool IsValidOpusBitrate(int bitrate) { return (bitrate >= kOpusMinBitrate && bitrate <= kOpusMaxBitrate); } @@ -429,6 +432,59 @@ static bool IsOpusFecEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamUseInbandFec, &value) && value == 1; } +// Returns kOpusDefaultPlaybackRate if params[kCodecParamMaxPlaybackRate] is not +// defined. Returns the value of params[kCodecParamMaxPlaybackRate] otherwise. +static int GetOpusMaxPlaybackRate(const AudioCodec& codec) { + int value; + if (codec.GetParam(kCodecParamMaxPlaybackRate, &value)) { + return value; + } + return kOpusDefaultMaxPlaybackRate; +} + +static void GetOpusConfig(const AudioCodec& codec, webrtc::CodecInst* voe_codec, + bool* enable_codec_fec, int* max_playback_rate) { + *enable_codec_fec = IsOpusFecEnabled(codec); + *max_playback_rate = GetOpusMaxPlaybackRate(codec); + + // If OPUS, change what we send according to the "stereo" codec + // parameter, and not the "channels" parameter. We set + // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If + // the bitrate is not specified, i.e. is zero, we set it to the + // appropriate default value for mono or stereo Opus. + + // TODO(minyue): The determination of bit rate might take the maximum playback + // rate into account. + + if (IsOpusStereoEnabled(codec)) { + voe_codec->channels = 2; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus stereo bitrate: " + << kOpusStereoBitrate; + } + voe_codec->rate = kOpusStereoBitrate; + } + } else { + voe_codec->channels = 1; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus mono bitrate: " + << kOpusMonoBitrate; + } + voe_codec->rate = kOpusMonoBitrate; + } + } + int bitrate_from_params = GetOpusBitrateFromParams(codec); + if (bitrate_from_params != 0) { + voe_codec->rate = bitrate_from_params; + } +} + void WebRtcVoiceEngine::ConstructCodecs() { LOG(LS_INFO) << "WebRtc VoiceEngine codecs:"; int ncodecs = voe_wrapper_->codec()->NumOfCodecs(); @@ -805,30 +861,6 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { } } - bool experimental_ns; - if (options.experimental_ns.Get(&experimental_ns)) { - webrtc::AudioProcessing* audioproc = - voe_wrapper_->base()->audio_processing(); -#ifdef USE_WEBRTC_DEV_BRANCH - webrtc::Config config; - config.Set<webrtc::ExperimentalNs>(new webrtc::ExperimentalNs( - experimental_ns)); - audioproc->SetExtraOptions(config); -#else - // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine - // returns NULL on audio_processing(). - if (audioproc) { - if (audioproc->EnableExperimentalNs(experimental_ns) == -1) { - LOG_RTCERR1(EnableExperimentalNs, experimental_ns); - return false; - } - } else { - LOG(LS_VERBOSE) << "Experimental noise suppression set to " - << experimental_ns; - } -#endif - } - bool highpass_filter; if (options.highpass_filter.Get(&highpass_filter)) { LOG(LS_INFO) << "High pass filter enabled? " << highpass_filter; @@ -874,20 +906,50 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { StopAecDump(); } + webrtc::Config config; + + experimental_aec_.SetFrom(options.experimental_aec); bool experimental_aec; - if (options.experimental_aec.Get(&experimental_aec)) { - LOG(LS_INFO) << "Experimental aec is " << experimental_aec; - webrtc::AudioProcessing* audioproc = - voe_wrapper_->base()->audio_processing(); + if (experimental_aec_.Get(&experimental_aec)) { + LOG(LS_INFO) << "Experimental aec is enabled? " << experimental_aec; + config.Set<webrtc::DelayCorrection>( + new webrtc::DelayCorrection(experimental_aec)); + } + +#ifdef USE_WEBRTC_DEV_BRANCH + experimental_ns_.SetFrom(options.experimental_ns); + bool experimental_ns; + if (experimental_ns_.Get(&experimental_ns)) { + LOG(LS_INFO) << "Experimental ns is enabled? " << experimental_ns; + config.Set<webrtc::ExperimentalNs>( + new webrtc::ExperimentalNs(experimental_ns)); + } +#endif + + // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine + // returns NULL on audio_processing(). + webrtc::AudioProcessing* audioproc = voe_wrapper_->base()->audio_processing(); + if (audioproc) { + audioproc->SetExtraOptions(config); + } + +#ifndef USE_WEBRTC_DEV_BRANCH + bool experimental_ns; + if (options.experimental_ns.Get(&experimental_ns)) { + LOG(LS_INFO) << "Experimental ns is enabled? " << experimental_ns; // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine // returns NULL on audio_processing(). if (audioproc) { - webrtc::Config config; - config.Set<webrtc::DelayCorrection>( - new webrtc::DelayCorrection(experimental_aec)); - audioproc->SetExtraOptions(config); + if (audioproc->EnableExperimentalNs(experimental_ns) == -1) { + LOG_RTCERR1(EnableExperimentalNs, experimental_ns); + return false; + } + } else { + LOG(LS_VERBOSE) << "Experimental noise suppression set to " + << experimental_ns; } } +#endif uint32 recording_sample_rate; if (options.recording_sample_rate.Get(&recording_sample_rate)) { @@ -1759,6 +1821,8 @@ WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine) typing_noise_detected_(false), desired_send_(SEND_NOTHING), send_(SEND_NOTHING), + shared_bwe_vie_(NULL), + shared_bwe_vie_channel_(-1), default_receive_ssrc_(0) { engine->RegisterChannel(this); LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel " @@ -1770,6 +1834,7 @@ WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine) WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() { LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel " << voe_channel(); + SetupSharedBandwidthEstimation(NULL, -1); // Remove any remaining send streams, the default channel will be deleted // later. @@ -1870,6 +1935,12 @@ bool WebRtcVoiceMediaChannel::SetOptions(const AudioOptions& options) { } } + // Force update of Video Engine BWE forwarding to reflect experiment setting. + if (!SetupSharedBandwidthEstimation(shared_bwe_vie_, + shared_bwe_vie_channel_)) { + return false; + } + LOG(LS_INFO) << "Set voice channel options. Current options: " << options_.ToString(); return true; @@ -1976,6 +2047,10 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( bool nack_enabled = nack_enabled_; bool enable_codec_fec = false; + // max_playback_rate <= 0 will not trigger setting of maximum encoding + // bandwidth. + int max_playback_rate = 0; + // Set send codec (the first non-telephone-event/CN codec) for (std::vector<AudioCodec>::const_iterator it = codecs.begin(); it != codecs.end(); ++it) { @@ -1992,40 +2067,6 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( continue; } - // If OPUS, change what we send according to the "stereo" codec - // parameter, and not the "channels" parameter. We set - // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If - // the bitrate is not specified, i.e. is zero, we set it to the - // appropriate default value for mono or stereo Opus. - if (IsOpus(*it)) { - if (IsOpusStereoEnabled(*it)) { - voe_codec.channels = 2; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus stereo bitrate: " - << kOpusStereoBitrate; - } - voe_codec.rate = kOpusStereoBitrate; - } - } else { - voe_codec.channels = 1; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus mono bitrate: " - << kOpusMonoBitrate; - } - voe_codec.rate = kOpusMonoBitrate; - } - } - int bitrate_from_params = GetOpusBitrateFromParams(*it); - if (bitrate_from_params != 0) { - voe_codec.rate = bitrate_from_params; - } - } // We'll use the first codec in the list to actually send audio data. // Be sure to use the payload type requested by the remote side. @@ -2055,8 +2096,11 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( } else { send_codec = voe_codec; nack_enabled = IsNackEnabled(*it); - // For Opus as the send codec, we enable inband FEC if requested. - enable_codec_fec = IsOpus(*it) && IsOpusFecEnabled(*it); + // For Opus as the send codec, we are to enable inband FEC if requested + // and set maximum playback rate. + if (IsOpus(*it)) { + GetOpusConfig(*it, &send_codec, &enable_codec_fec, &max_playback_rate); + } } found_send_codec = true; break; @@ -2090,6 +2134,21 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( #endif // USE_WEBRTC_DEV_BRANCH } + // maxplaybackrate should be set after SetSendCodec. + if (max_playback_rate > 0) { + LOG(LS_INFO) << "Attempt to set maximum playback rate to " + << max_playback_rate + << " Hz on channel " + << channel; +#ifdef USE_WEBRTC_DEV_BRANCH + // (max_playback_rate + 1) >> 1 is to obtain ceil(max_playback_rate / 2.0). + if (engine()->voe()->codec()->SetOpusMaxPlaybackRate( + channel, max_playback_rate) == -1) { + LOG(LS_WARNING) << "Could not set maximum playback rate."; + } +#endif + } + // Always update the |send_codec_| to the currently set send codec. send_codec_.reset(new webrtc::CodecInst(send_codec)); @@ -2531,8 +2590,8 @@ bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) { } if (engine()->voe()->rtp()->SetRTCP_CNAME(channel, sp.cname.c_str()) == -1) { - LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname); - return false; + LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname); + return false; } // Set the current codecs to be used for the new channel. @@ -2604,6 +2663,9 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { receive_channels_.insert(std::make_pair( default_receive_ssrc_, new WebRtcVoiceChannelRenderer(voe_channel(), audio_transport))); + if (!SetupSharedBweOnChannel(voe_channel())) { + return false; + } return SetPlayout(voe_channel(), playout_); } @@ -2691,6 +2753,11 @@ bool WebRtcVoiceMediaChannel::ConfigureRecvChannel(int channel) { return false; } + // Set up channel to be able to forward incoming packets to video engine BWE. + if (!SetupSharedBweOnChannel(channel)) { + return false; + } + return SetPlayout(channel, playout_); } @@ -3060,7 +3127,8 @@ void WebRtcVoiceMediaChannel::OnPacketReceived( engine()->voe()->network()->ReceivedRTPPacket( which_channel, packet->data(), - static_cast<unsigned int>(packet->length())); + static_cast<unsigned int>(packet->length()), + webrtc::PacketTime(packet_time.timestamp, packet_time.not_before)); } void WebRtcVoiceMediaChannel::OnRtcpReceived( @@ -3454,6 +3522,23 @@ int WebRtcVoiceMediaChannel::GetSendChannelNum(uint32 ssrc) { return -1; } +bool WebRtcVoiceMediaChannel::SetupSharedBandwidthEstimation( + webrtc::VideoEngine* vie, int vie_channel) { + shared_bwe_vie_ = vie; + shared_bwe_vie_channel_ = vie_channel; + + if (!SetupSharedBweOnChannel(voe_channel())) { + return false; + } + for (ChannelMap::iterator it = receive_channels_.begin(); + it != receive_channels_.end(); ++it) { + if (!SetupSharedBweOnChannel(it->second->channel())) { + return false; + } + } + return true; +} + bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec, const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) { // Get the RED encodings from the parameter with no name. This may @@ -3603,6 +3688,25 @@ bool WebRtcVoiceMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter, return true; } +bool WebRtcVoiceMediaChannel::SetupSharedBweOnChannel(int voe_channel) { + webrtc::ViENetwork* vie_network = NULL; + int vie_channel = -1; + if (options_.combined_audio_video_bwe.GetWithDefaultIfUnset(false) && + shared_bwe_vie_ != NULL && shared_bwe_vie_channel_ != -1) { + vie_network = webrtc::ViENetwork::GetInterface(shared_bwe_vie_); + vie_channel = shared_bwe_vie_channel_; + } + if (engine()->voe()->rtp()->SetVideoEngineBWETarget(voe_channel, vie_network, + vie_channel) == -1) { + LOG_RTCERR3(SetVideoEngineBWETarget, voe_channel, vie_network, vie_channel); + if (vie_network != NULL) { + // Don't fail if we're tearing down. + return false; + } + } + return true; +} + int WebRtcSoundclipStream::Read(void *buf, int len) { size_t res = 0; mem_.Read(buf, len, &res, NULL); diff --git a/media/webrtc/webrtcvoiceengine.h b/media/webrtc/webrtcvoiceengine.h index 69705cc..5557af0 100644 --- a/media/webrtc/webrtcvoiceengine.h +++ b/media/webrtc/webrtcvoiceengine.h @@ -54,6 +54,10 @@ #error "Bogus include." #endif +namespace webrtc { +class VideoEngine; +} + namespace cricket { // WebRtcSoundclipStream is an adapter object that allows a memory stream to be @@ -280,6 +284,13 @@ class WebRtcVoiceEngine uint32 rx_processor_ssrc_; rtc::CriticalSection signal_media_critical_; + + // Cache received experimental_aec and experimental_ns values, and apply them + // in case they are missing in the audio options. We need to do this because + // SetExtraOptions() will revert to defaults for options which are not + // provided. + Settable<bool> experimental_aec_; + Settable<bool> experimental_ns_; }; // WebRtcMediaChannel is a class that implements the common WebRtc channel @@ -377,6 +388,8 @@ class WebRtcVoiceMediaChannel int GetReceiveChannelNum(uint32 ssrc); int GetSendChannelNum(uint32 ssrc); + bool SetupSharedBandwidthEstimation(webrtc::VideoEngine* vie, + int vie_channel); protected: int GetLastEngineError() { return engine()->GetLastEngineError(); } int GetOutputLevel(int channel); @@ -419,6 +432,7 @@ class WebRtcVoiceMediaChannel bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id, const RtpHeaderExtension* extension); + bool SetupSharedBweOnChannel(int voe_channel); bool SetChannelRecvRtpHeaderExtensions( int channel_id, @@ -442,6 +456,11 @@ class WebRtcVoiceMediaChannel bool typing_noise_detected_; SendFlags desired_send_; SendFlags send_; + // shared_bwe_vie_ and shared_bwe_vie_channel_ together identifies a WebRTC + // VideoEngine channel that this voice channel should forward incoming packets + // to for Bandwidth Estimation purposes. + webrtc::VideoEngine* shared_bwe_vie_; + int shared_bwe_vie_channel_; // send_channels_ contains the channels which are being used for sending. // When the default channel (voe_channel) is used for sending, it is diff --git a/media/webrtc/webrtcvoiceengine_unittest.cc b/media/webrtc/webrtcvoiceengine_unittest.cc index 89d4c4d..b044e92 100644 --- a/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/media/webrtc/webrtcvoiceengine_unittest.cc @@ -38,6 +38,7 @@ #include "talk/media/base/fakenetworkinterface.h" #include "talk/media/base/fakertp.h" #include "talk/media/webrtc/fakewebrtcvoiceengine.h" +#include "talk/media/webrtc/webrtcvie.h" #include "talk/media/webrtc/webrtcvoiceengine.h" #include "talk/p2p/base/fakesession.h" #include "talk/session/media/channel.h" @@ -1151,7 +1152,6 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecNoOpusFec) { int channel_num = voe_.GetLastChannel(); std::vector<cricket::AudioCodec> codecs; codecs.push_back(kOpusCodec); - codecs[0].bitrate = 0; EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } @@ -1228,6 +1228,159 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecIsacWithParamNoFec) { EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } + +// Test that Opus FEC status can be changed. +TEST_F(WebRtcVoiceEngineTestFake, ChangeOpusFecStatus) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); + codecs[0].params["useinbandfec"] = "1"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(voe_.GetCodecFEC(channel_num)); +} + +// Test maxplaybackrate <= 8000 triggers Opus narrow band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateNb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 8000 < maxplaybackrate <= 12000 triggers Opus medium band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateMb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthMb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 12000 < maxplaybackrate <= 16000 triggers Opus wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateWb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 12001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthWb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 16000 < maxplaybackrate <= 24000 triggers Opus super wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateSwb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 16001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthSwb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 24000 < maxplaybackrate triggers Opus full band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateFb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 24001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test Opus that without maxplaybackrate, default playback rate is used. +TEST_F(WebRtcVoiceEngineTestFake, DefaultOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test the with non-Opus, maxplaybackrate has no effect. +TEST_F(WebRtcVoiceEngineTestFake, SetNonOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kIsacCodec); + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 32000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(0, voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test maxplaybackrate can be set on two streams. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateOnTwoStreams) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + // Default bandwidth is 24000. + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + channel_->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc2)); + channel_num = voe_.GetLastChannel(); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} #endif // USE_WEBRTC_DEV_BRANCH // Test that we can apply CELT with stereo mode but fail with mono mode. @@ -3160,3 +3313,113 @@ TEST(WebRtcVoiceEngineTest, CoInitialize) { CoUninitialize(); } #endif + +TEST_F(WebRtcVoiceEngineTestFake, ChangeCombinedAudioVideoBweOption) { + // Test that changing the combined_audio_video_bwe option results in the + // expected state changes in VoiceEngine. + cricket::ViEWrapper vie; + const int kVieCh = 667; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(), + kVieCh)); + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(2))); + int recv_ch = voe_.GetLastChannel(); + + // Combined BWE should not be set up yet. + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + // Enable combined BWE option - now it should be set up. + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch)); + + // Disable combined BWE option - should be disabled again. + options.combined_audio_video_bwe.Set(false); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); +} + +TEST_F(WebRtcVoiceEngineTestFake, SetupSharedBandwidthEstimation) { + // Test that calling SetupSharedBandwidthEstimation() on the voice media + // channel results in the expected state changes in VoiceEngine. + cricket::ViEWrapper vie1; + cricket::ViEWrapper vie2; + const int kVieCh1 = 667; + const int kVieCh2 = 70; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(2))); + int recv_ch = voe_.GetLastChannel(); + + // Combined BWE should not be set up yet. + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + // Register - should be enabled. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie1.engine(), + kVieCh1)); + EXPECT_EQ(vie1.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh1, voe_.GetVideoChannel(recv_ch)); + + // Re-register - should still be enabled. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie2.engine(), + kVieCh2)); + EXPECT_EQ(vie2.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh2, voe_.GetVideoChannel(recv_ch)); + + // Unregister - should be disabled again. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); +} + +TEST_F(WebRtcVoiceEngineTestFake, ConfigureCombinedBweForNewRecvStreams) { + // Test that adding receive streams after enabling combined bandwidth + // estimation will correctly configure each channel. + cricket::ViEWrapper vie; + const int kVieCh = 667; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(), + kVieCh)); + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + + static const uint32 kSsrcs[] = {1, 2, 3, 4}; + int voe_channels[ARRAY_SIZE(kSsrcs)] = {0}; + for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs[i]))); + int recv_ch = media_channel->GetReceiveChannelNum(kSsrcs[i]); + EXPECT_NE(-1, recv_ch); + voe_channels[i] = recv_ch; + EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch)); + } + + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); + + for (unsigned int i = 0; i < ARRAY_SIZE(voe_channels); ++i) { + EXPECT_EQ(NULL, voe_.GetViENetwork(voe_channels[i])); + EXPECT_EQ(-1, voe_.GetVideoChannel(voe_channels[i])); + } +} diff --git a/p2p/base/asyncstuntcpsocket.h b/p2p/base/asyncstuntcpsocket.h index b63c0b5..136b4df 100644 --- a/p2p/base/asyncstuntcpsocket.h +++ b/p2p/base/asyncstuntcpsocket.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_BASE_ASYNCSTUNTCPSOCKET_H_ -#define TALK_BASE_ASYNCSTUNTCPSOCKET_H_ +#ifndef TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ +#define TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ #include "webrtc/base/asynctcpsocket.h" #include "webrtc/base/scoped_ptr.h" @@ -64,4 +64,4 @@ class AsyncStunTCPSocket : public rtc::AsyncTCPSocketBase { } // namespace cricket -#endif // TALK_BASE_ASYNCSTUNTCPSOCKET_H_ +#endif // TALK_P2P_BASE_ASYNCSTUNTCPSOCKET_H_ diff --git a/p2p/base/basicpacketsocketfactory.h b/p2p/base/basicpacketsocketfactory.h index b1bae35..77b1652 100644 --- a/p2p/base/basicpacketsocketfactory.h +++ b/p2p/base/basicpacketsocketfactory.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_BASE_BASICPACKETSOCKETFACTORY_H_ -#define TALK_BASE_BASICPACKETSOCKETFACTORY_H_ +#ifndef TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ +#define TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ #include "talk/p2p/base/packetsocketfactory.h" @@ -65,4 +65,4 @@ class BasicPacketSocketFactory : public PacketSocketFactory { } // namespace rtc -#endif // TALK_BASE_BASICPACKETSOCKETFACTORY_H_ +#endif // TALK_P2P_BASE_BASICPACKETSOCKETFACTORY_H_ diff --git a/p2p/base/constants.cc b/p2p/base/constants.cc index 2e57d9d..278a615 100644 --- a/p2p/base/constants.cc +++ b/p2p/base/constants.cc @@ -29,7 +29,7 @@ #include <string> -#include "talk/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/qname.h" namespace cricket { diff --git a/p2p/base/constants.h b/p2p/base/constants.h index 61dd815..4cd1166 100644 --- a/p2p/base/constants.h +++ b/p2p/base/constants.h @@ -29,7 +29,7 @@ #define TALK_P2P_BASE_CONSTANTS_H_ #include <string> -#include "talk/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/qname.h" // This file contains constants related to signaling that are used in various // classes in this directory. diff --git a/p2p/base/p2ptransport.cc b/p2p/base/p2ptransport.cc index b992cc0..06941ac 100644 --- a/p2p/base/p2ptransport.cc +++ b/p2p/base/p2ptransport.cc @@ -35,8 +35,8 @@ #include "talk/p2p/base/parsing.h" #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/base/sessionmessages.h" -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/base64.h" #include "webrtc/base/common.h" diff --git a/p2p/base/packetsocketfactory.h b/p2p/base/packetsocketfactory.h index 6b82682..46767c2 100644 --- a/p2p/base/packetsocketfactory.h +++ b/p2p/base/packetsocketfactory.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_BASE_PACKETSOCKETFACTORY_H_ -#define TALK_BASE_PACKETSOCKETFACTORY_H_ +#ifndef TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ +#define TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ #include "webrtc/base/proxyinfo.h" @@ -66,4 +66,4 @@ class PacketSocketFactory { } // namespace rtc -#endif // TALK_BASE_PACKETSOCKETFACTORY_H_ +#endif // TALK_P2P_BASE_PACKETSOCKETFACTORY_H_ diff --git a/p2p/base/parsing.h b/p2p/base/parsing.h index 635b122..2aab2f8 100644 --- a/p2p/base/parsing.h +++ b/p2p/base/parsing.h @@ -30,7 +30,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" // Needed to delete ParseError.extra. +#include "webrtc/libjingle/xmllite/xmlelement.h" // Needed to delete ParseError.extra. #include "webrtc/base/basictypes.h" #include "webrtc/base/stringencode.h" diff --git a/p2p/base/port.cc b/p2p/base/port.cc index 3820154..0cf46eb 100644 --- a/p2p/base/port.cc +++ b/p2p/base/port.cc @@ -1212,7 +1212,7 @@ std::string Connection::ToString() const { << ":" << local.type() << ":" << local.protocol() << ":" << local.address().ToSensitiveString() << "->" << remote.id() << ":" << remote.component() - << ":" << remote.preference() + << ":" << remote.priority() << ":" << remote.type() << ":" << remote.protocol() << ":" << remote.address().ToSensitiveString() << "|" << CONNECT_STATE_ABBREV[connected()] diff --git a/p2p/base/port.h b/p2p/base/port.h index cccfdad..4893586 100644 --- a/p2p/base/port.h +++ b/p2p/base/port.h @@ -155,6 +155,7 @@ class Port : public PortInterface, public rtc::MessageHandler, uint64 IceTiebreaker() const { return tiebreaker_; } virtual bool SharedSocket() const { return shared_socket_; } + void ResetSharedSocket() { shared_socket_ = false; } // The thread on which this port performs its I/O. rtc::Thread* thread() { return thread_; } diff --git a/p2p/base/portallocator.h b/p2p/base/portallocator.h index 84e5fea..5bc389e 100644 --- a/p2p/base/portallocator.h +++ b/p2p/base/portallocator.h @@ -44,17 +44,18 @@ namespace cricket { // Clients can override this class to control port allocation, including // what kinds of ports are allocated. -const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01; -const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02; -const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04; -const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08; -const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10; -const uint32 PORTALLOCATOR_ENABLE_BUNDLE = 0x20; -const uint32 PORTALLOCATOR_ENABLE_IPV6 = 0x40; -const uint32 PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80; -const uint32 PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100; -const uint32 PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200; -const uint32 PORTALLOCATOR_ENABLE_TURN_SHARED_SOCKET = 0x400; +enum { + PORTALLOCATOR_DISABLE_UDP = 0x01, + PORTALLOCATOR_DISABLE_STUN = 0x02, + PORTALLOCATOR_DISABLE_RELAY = 0x04, + PORTALLOCATOR_DISABLE_TCP = 0x08, + PORTALLOCATOR_ENABLE_SHAKER = 0x10, + PORTALLOCATOR_ENABLE_BUNDLE = 0x20, + PORTALLOCATOR_ENABLE_IPV6 = 0x40, + PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80, + PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100, + PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200, +}; const uint32 kDefaultPortAllocatorFlags = 0; @@ -63,6 +64,15 @@ const uint32 kDefaultStepDelay = 1000; // 1 sec step delay. // internal. Less than 20ms is not acceptable. We choose 50ms as our default. const uint32 kMinimumStepDelay = 50; +// CF = CANDIDATE FILTER +enum { + CF_NONE = 0x0, + CF_HOST = 0x1, + CF_REFLEXIVE = 0x2, + CF_RELAY = 0x4, + CF_ALL = 0x7, +}; + class PortAllocatorSessionMuxer; class PortAllocatorSession : public sigslot::has_slots<> { @@ -118,7 +128,8 @@ class PortAllocator : public sigslot::has_slots<> { min_port_(0), max_port_(0), step_delay_(kDefaultStepDelay), - allow_tcp_listen_(true) { + allow_tcp_listen_(true), + candidate_filter_(CF_ALL) { // This will allow us to have old behavior on non webrtc clients. } virtual ~PortAllocator(); @@ -158,7 +169,6 @@ class PortAllocator : public sigslot::has_slots<> { uint32 step_delay() const { return step_delay_; } void set_step_delay(uint32 delay) { - ASSERT(delay >= kMinimumStepDelay); step_delay_ = delay; } @@ -167,6 +177,13 @@ class PortAllocator : public sigslot::has_slots<> { allow_tcp_listen_ = allow_tcp_listen; } + uint32 candidate_filter() { return candidate_filter_; } + bool set_candidate_filter(uint32 filter) { + // TODO(mallinath) - Do transition check? + candidate_filter_ = filter; + return true; + } + protected: virtual PortAllocatorSession* CreateSessionInternal( const std::string& content_name, @@ -184,6 +201,7 @@ class PortAllocator : public sigslot::has_slots<> { uint32 step_delay_; SessionMuxerMap muxers_; bool allow_tcp_listen_; + uint32 candidate_filter_; }; } // namespace cricket diff --git a/p2p/base/rawtransport.cc b/p2p/base/rawtransport.cc index 5913177..2af1864 100644 --- a/p2p/base/rawtransport.cc +++ b/p2p/base/rawtransport.cc @@ -32,8 +32,8 @@ #include "talk/p2p/base/rawtransport.h" #include "talk/p2p/base/rawtransportchannel.h" #include "talk/p2p/base/sessionmanager.h" -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/common.h" diff --git a/p2p/base/rawtransportchannel.cc b/p2p/base/rawtransportchannel.cc index ef0a532..ae268e3 100644 --- a/p2p/base/rawtransportchannel.cc +++ b/p2p/base/rawtransportchannel.cc @@ -36,8 +36,8 @@ #include "talk/p2p/base/relayport.h" #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/base/stunport.h" -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/common.h" diff --git a/p2p/base/session.h b/p2p/base/session.h index 756bbe0..e06cf00 100644 --- a/p2p/base/session.h +++ b/p2p/base/session.h @@ -39,7 +39,7 @@ #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/base/sessionmessages.h" #include "talk/p2p/base/transport.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/refcount.h" #include "webrtc/base/scoped_ptr.h" diff --git a/p2p/base/session_unittest.cc b/p2p/base/session_unittest.cc index 90cd06d..4674d2c 100644 --- a/p2p/base/session_unittest.cc +++ b/p2p/base/session_unittest.cc @@ -601,8 +601,7 @@ class TestPortAllocatorSession : public cricket::PortAllocatorSession { network_("network", "unittest", rtc::IPAddress(INADDR_LOOPBACK), 8), socket_factory_(rtc::Thread::Current()), - running_(false), - port_(28653) { + running_(false) { network_.AddIP(address_.ipaddr()); } @@ -655,7 +654,6 @@ class TestPortAllocatorSession : public cricket::PortAllocatorSession { rtc::Network network_; rtc::BasicPacketSocketFactory socket_factory_; bool running_; - int port_; }; class TestPortAllocator : public cricket::PortAllocator { diff --git a/p2p/base/sessiondescription.cc b/p2p/base/sessiondescription.cc index 7009aa8..7ad3d48 100644 --- a/p2p/base/sessiondescription.cc +++ b/p2p/base/sessiondescription.cc @@ -27,7 +27,7 @@ #include "talk/p2p/base/sessiondescription.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" namespace cricket { diff --git a/p2p/base/sessionmessages.cc b/p2p/base/sessionmessages.cc index bfba273..b2fd9d6 100644 --- a/p2p/base/sessionmessages.cc +++ b/p2p/base/sessionmessages.cc @@ -36,7 +36,7 @@ #include "talk/p2p/base/sessionclient.h" #include "talk/p2p/base/sessiondescription.h" #include "talk/p2p/base/transport.h" -#include "talk/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" #include "talk/xmpp/constants.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" diff --git a/p2p/base/sessionmessages.h b/p2p/base/sessionmessages.h index 38fca98..7e1f8ac 100644 --- a/p2p/base/sessionmessages.h +++ b/p2p/base/sessionmessages.h @@ -36,7 +36,7 @@ #include "talk/p2p/base/parsing.h" #include "talk/p2p/base/sessiondescription.h" // Needed to delete contents. #include "talk/p2p/base/transportinfo.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/basictypes.h" namespace cricket { diff --git a/p2p/base/stun.cc b/p2p/base/stun.cc index be96b76..061fd9a 100644 --- a/p2p/base/stun.cc +++ b/p2p/base/stun.cc @@ -41,6 +41,7 @@ using rtc::ByteBuffer; namespace cricket { +const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[] = "Try Alternate Server"; const char STUN_ERROR_REASON_BAD_REQUEST[] = "Bad Request"; const char STUN_ERROR_REASON_UNAUTHORIZED[] = "Unauthorized"; const char STUN_ERROR_REASON_FORBIDDEN[] = "Forbidden"; @@ -401,7 +402,7 @@ StunAttributeValueType StunMessage::GetAttributeValueType(int type) const { case STUN_ATTR_NONCE: return STUN_VALUE_BYTE_STRING; case STUN_ATTR_XOR_MAPPED_ADDRESS: return STUN_VALUE_XOR_ADDRESS; case STUN_ATTR_SOFTWARE: return STUN_VALUE_BYTE_STRING; - case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_BYTE_STRING; + case STUN_ATTR_ALTERNATE_SERVER: return STUN_VALUE_ADDRESS; case STUN_ATTR_FINGERPRINT: return STUN_VALUE_UINT32; case STUN_ATTR_RETRANSMIT_COUNT: return STUN_VALUE_UINT32; default: return STUN_VALUE_UNKNOWN; diff --git a/p2p/base/stun.h b/p2p/base/stun.h index b22b51e..c4f522b 100644 --- a/p2p/base/stun.h +++ b/p2p/base/stun.h @@ -63,7 +63,7 @@ enum StunAttributeType { STUN_ATTR_NONCE = 0x0015, // ByteString STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress STUN_ATTR_SOFTWARE = 0x8022, // ByteString - STUN_ATTR_ALTERNATE_SERVER = 0x8023, // ByteString + STUN_ATTR_ALTERNATE_SERVER = 0x8023, // Address STUN_ATTR_FINGERPRINT = 0x8028, // UInt32 STUN_ATTR_RETRANSMIT_COUNT = 0xFF00 // UInt32 }; @@ -104,6 +104,7 @@ enum StunErrorCode { }; // Strings for the error codes above. +extern const char STUN_ERROR_REASON_TRY_ALTERNATE_SERVER[]; extern const char STUN_ERROR_REASON_BAD_REQUEST[]; extern const char STUN_ERROR_REASON_UNAUTHORIZED[]; extern const char STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE[]; diff --git a/p2p/base/stunport.h b/p2p/base/stunport.h index 0a49b67..b3b6d5b 100644 --- a/p2p/base/stunport.h +++ b/p2p/base/stunport.h @@ -82,7 +82,7 @@ class UDPPort : public Port { return socket_->GetLocalAddress(); } - const ServerAddresses server_addresses() const { + const ServerAddresses& server_addresses() const { return server_addresses_; } void diff --git a/p2p/base/testturnserver.h b/p2p/base/testturnserver.h index e2c0ccb..6c30afe 100644 --- a/p2p/base/testturnserver.h +++ b/p2p/base/testturnserver.h @@ -29,6 +29,7 @@ #define TALK_P2P_BASE_TESTTURNSERVER_H_ #include <string> +#include <vector> #include "talk/p2p/base/basicpacketsocketfactory.h" #include "talk/p2p/base/stun.h" @@ -41,6 +42,27 @@ namespace cricket { static const char kTestRealm[] = "example.org"; static const char kTestSoftware[] = "TestTurnServer"; +class TestTurnRedirector : public TurnRedirectInterface { + public: + explicit TestTurnRedirector(const std::vector<rtc::SocketAddress>& addresses) + : alternate_server_addresses_(addresses), + iter_(alternate_server_addresses_.begin()) { + } + + virtual bool ShouldRedirect(const rtc::SocketAddress&, + rtc::SocketAddress* out) { + if (!out || iter_ == alternate_server_addresses_.end()) { + return false; + } + *out = *iter_++; + return true; + } + + private: + const std::vector<rtc::SocketAddress>& alternate_server_addresses_; + std::vector<rtc::SocketAddress>::const_iterator iter_; +}; + class TestTurnServer : public TurnAuthInterface { public: TestTurnServer(rtc::Thread* thread, @@ -61,6 +83,10 @@ class TestTurnServer : public TurnAuthInterface { TurnServer* server() { return &server_; } + void set_redirect_hook(TurnRedirectInterface* redirect_hook) { + server_.set_redirect_hook(redirect_hook); + } + void AddInternalSocket(const rtc::SocketAddress& int_addr, ProtocolType proto) { rtc::Thread* thread = rtc::Thread::Current(); diff --git a/p2p/base/transport.cc b/p2p/base/transport.cc index 6ee7b2a..d88f5e7 100644 --- a/p2p/base/transport.cc +++ b/p2p/base/transport.cc @@ -33,7 +33,7 @@ #include "talk/p2p/base/port.h" #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/base/transportchannelimpl.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/bind.h" #include "webrtc/base/common.h" diff --git a/p2p/base/transport_unittest.cc b/p2p/base/transport_unittest.cc index fa2c116..8f7dae2 100644 --- a/p2p/base/transport_unittest.cc +++ b/p2p/base/transport_unittest.cc @@ -31,7 +31,7 @@ #include "talk/p2p/base/parsing.h" #include "talk/p2p/base/rawtransport.h" #include "talk/p2p/base/sessionmessages.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/fakesslidentity.h" #include "webrtc/base/gunit.h" diff --git a/p2p/base/turnport.cc b/p2p/base/turnport.cc index 3faacd1..2908d71 100644 --- a/p2p/base/turnport.cc +++ b/p2p/base/turnport.cc @@ -51,6 +51,10 @@ static const int TURN_PERMISSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes static const size_t TURN_CHANNEL_HEADER_SIZE = 4U; +// Retry at most twice (i.e. three different ALLOCATE requests) on +// STUN_ERROR_ALLOCATION_MISMATCH error per rfc5766. +static const size_t MAX_ALLOCATE_MISMATCH_RETRIES = 2; + inline bool IsTurnChannelData(uint16 msg_type) { return ((msg_type & 0xC000) == 0x4000); // MSB are 0b01 } @@ -78,6 +82,7 @@ class TurnAllocateRequest : public StunRequest { private: // Handles authentication challenge from the server. void OnAuthChallenge(StunMessage* response, int code); + void OnTryAlternate(StunMessage* response, int code); void OnUnknownAttribute(StunMessage* response); TurnPort* port_; @@ -187,7 +192,8 @@ TurnPort::TurnPort(rtc::Thread* thread, request_manager_(thread), next_channel_number_(TURN_CHANNEL_NUMBER_START), connected_(false), - server_priority_(server_priority) { + server_priority_(server_priority), + allocate_mismatch_retries_(0) { request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket); } @@ -211,7 +217,8 @@ TurnPort::TurnPort(rtc::Thread* thread, request_manager_(thread), next_channel_number_(TURN_CHANNEL_NUMBER_START), connected_(false), - server_priority_(server_priority) { + server_priority_(server_priority), + allocate_mismatch_retries_(0) { request_manager_.SignalSendPacket.connect(this, &TurnPort::OnSendStunPacket); } @@ -253,6 +260,9 @@ void TurnPort::PrepareAddress() { return; } + // Insert the current address to prevent redirection pingpong. + attempted_server_addresses_.insert(server_address_.address); + LOG_J(LS_INFO, this) << "Trying to connect to TURN server via " << ProtoToString(server_address_.proto) << " @ " << server_address_.address.ToSensitiveString(); @@ -267,6 +277,8 @@ void TurnPort::PrepareAddress() { } bool TurnPort::CreateTurnClientSocket() { + ASSERT(!socket_ || SharedSocket()); + if (server_address_.proto == PROTO_UDP && !SharedSocket()) { socket_ = socket_factory()->CreateUdpSocket( rtc::SocketAddress(ip(), 0), min_port(), max_port()); @@ -336,6 +348,29 @@ void TurnPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) { } } +void TurnPort::OnAllocateMismatch() { + if (allocate_mismatch_retries_ >= MAX_ALLOCATE_MISMATCH_RETRIES) { + LOG_J(LS_WARNING, this) << "Giving up on the port after " + << allocate_mismatch_retries_ + << " retries for STUN_ERROR_ALLOCATION_MISMATCH"; + OnAllocateError(); + return; + } + + LOG_J(LS_INFO, this) << "Allocating a new socket after " + << "STUN_ERROR_ALLOCATION_MISMATCH, retry = " + << allocate_mismatch_retries_ + 1; + if (SharedSocket()) { + ResetSharedSocket(); + } else { + delete socket_; + } + socket_ = NULL; + + PrepareAddress(); + ++allocate_mismatch_retries_; +} + Connection* TurnPort::CreateConnection(const Candidate& address, CandidateOrigin origin) { // TURN-UDP can only connect to UDP candidates. @@ -458,6 +493,38 @@ void TurnPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) { } } + +// Update current server address port with the alternate server address port. +bool TurnPort::SetAlternateServer(const rtc::SocketAddress& address) { + // Check if we have seen this address before and reject if we did. + AttemptedServerSet::iterator iter = attempted_server_addresses_.find(address); + if (iter != attempted_server_addresses_.end()) { + LOG_J(LS_WARNING, this) << "Redirection to [" + << address.ToSensitiveString() + << "] ignored, allocation failed."; + return false; + } + + // If protocol family of server address doesn't match with local, return. + if (!IsCompatibleAddress(address)) { + LOG(LS_WARNING) << "Server IP address family does not match with " + << "local host address family type"; + return false; + } + + LOG_J(LS_INFO, this) << "Redirecting from TURN server [" + << server_address_.address.ToSensitiveString() + << "] to TURN server [" + << address.ToSensitiveString() + << "]"; + server_address_ = ProtocolAddress(address, server_address_.proto, + server_address_.secure); + + // Insert the current address to prevent redirection pingpong. + attempted_server_addresses_.insert(server_address_.address); + return true; +} + void TurnPort::ResolveTurnAddress(const rtc::SocketAddress& address) { if (resolver_) return; @@ -544,6 +611,9 @@ void TurnPort::OnMessage(rtc::Message* message) { if (message->message_id == MSG_ERROR) { SignalPortError(this); return; + } else if (message->message_id == MSG_ALLOCATE_MISMATCH) { + OnAllocateMismatch(); + return; } Port::OnMessage(message); @@ -805,6 +875,14 @@ void TurnAllocateRequest::OnErrorResponse(StunMessage* response) { case STUN_ERROR_UNAUTHORIZED: // Unauthrorized. OnAuthChallenge(response, error_code->code()); break; + case STUN_ERROR_TRY_ALTERNATE: + OnTryAlternate(response, error_code->code()); + break; + case STUN_ERROR_ALLOCATION_MISMATCH: + // We must handle this error async because trying to delete the socket in + // OnErrorResponse will cause a deadlock on the socket. + port_->thread()->Post(port_, TurnPort::MSG_ALLOCATE_MISMATCH); + break; default: LOG_J(LS_WARNING, port_) << "Allocate response error, code=" << error_code->code(); @@ -849,6 +927,57 @@ void TurnAllocateRequest::OnAuthChallenge(StunMessage* response, int code) { port_->SendRequest(new TurnAllocateRequest(port_), 0); } +void TurnAllocateRequest::OnTryAlternate(StunMessage* response, int code) { + // TODO(guoweis): Currently, we only support UDP redirect + if (port_->server_address().proto != PROTO_UDP) { + LOG_J(LS_WARNING, port_) << "Receiving 300 Alternate Server on non-UDP " + << "allocating request from [" + << port_->server_address().address.ToSensitiveString() + << "], failed as currently not supported"; + port_->OnAllocateError(); + return; + } + + // According to RFC 5389 section 11, there are use cases where + // authentication of response is not possible, we're not validating + // message integrity. + + // Get the alternate server address attribute value. + const StunAddressAttribute* alternate_server_attr = + response->GetAddress(STUN_ATTR_ALTERNATE_SERVER); + if (!alternate_server_attr) { + LOG_J(LS_WARNING, port_) << "Missing STUN_ATTR_ALTERNATE_SERVER " + << "attribute in try alternate error response"; + port_->OnAllocateError(); + return; + } + if (!port_->SetAlternateServer(alternate_server_attr->GetAddress())) { + port_->OnAllocateError(); + return; + } + + // Check the attributes. + const StunByteStringAttribute* realm_attr = + response->GetByteString(STUN_ATTR_REALM); + if (realm_attr) { + LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_REALM attribute in " + << "try alternate error response."; + port_->set_realm(realm_attr->GetString()); + } + + const StunByteStringAttribute* nonce_attr = + response->GetByteString(STUN_ATTR_NONCE); + if (nonce_attr) { + LOG_J(LS_INFO, port_) << "Applying STUN_ATTR_NONCE attribute in " + << "try alternate error response."; + port_->set_nonce(nonce_attr->GetString()); + } + + // Send another allocate request to alternate server, + // with the received realm and nonce values. + port_->SendRequest(new TurnAllocateRequest(port_), 0); +} + TurnRefreshRequest::TurnRefreshRequest(TurnPort* port) : StunRequest(new TurnMessage()), port_(port) { @@ -876,7 +1005,6 @@ void TurnRefreshRequest::OnResponse(StunMessage* response) { } void TurnRefreshRequest::OnErrorResponse(StunMessage* response) { - // TODO(juberti): Handle 437 error response as a success. const StunErrorCodeAttribute* error_code = response->GetErrorCode(); LOG_J(LS_WARNING, port_) << "Refresh response error, code=" << error_code->code(); diff --git a/p2p/base/turnport.h b/p2p/base/turnport.h index d73b11d..ab7d4e7 100644 --- a/p2p/base/turnport.h +++ b/p2p/base/turnport.h @@ -30,6 +30,7 @@ #include <stdio.h> #include <list> +#include <set> #include <string> #include "talk/p2p/base/port.h" @@ -119,6 +120,12 @@ class TurnPort : public Port { int error() const { return error_; } + void OnAllocateMismatch(); + + rtc::AsyncPacketSocket* socket() const { + return socket_; + } + // Signal with resolved server address. // Parameters are port, server address and resolved server address. // This signal will be sent only if server address is resolved successfully. @@ -153,10 +160,14 @@ class TurnPort : public Port { int server_priority); private: - enum { MSG_ERROR = MSG_FIRST_AVAILABLE }; + enum { + MSG_ERROR = MSG_FIRST_AVAILABLE, + MSG_ALLOCATE_MISMATCH + }; typedef std::list<TurnEntry*> EntryList; typedef std::map<rtc::Socket::Option, int> SocketOptionsMap; + typedef std::set<rtc::SocketAddress> AttemptedServerSet; virtual void OnMessage(rtc::Message* pmsg); @@ -170,6 +181,7 @@ class TurnPort : public Port { } } + bool SetAlternateServer(const rtc::SocketAddress& address); void ResolveTurnAddress(const rtc::SocketAddress& address); void OnResolveResult(rtc::AsyncResolverInterface* resolver); @@ -207,6 +219,7 @@ class TurnPort : public Port { ProtocolAddress server_address_; RelayCredentials credentials_; + AttemptedServerSet attempted_server_addresses_; rtc::AsyncPacketSocket* socket_; SocketOptionsMap socket_options_; @@ -226,6 +239,9 @@ class TurnPort : public Port { // calculating the candidate priority. int server_priority_; + // The number of retries made due to allocate mismatch error. + size_t allocate_mismatch_retries_; + friend class TurnEntry; friend class TurnAllocateRequest; friend class TurnRefreshRequest; diff --git a/p2p/base/turnport_unittest.cc b/p2p/base/turnport_unittest.cc index 44dc64f..d895cbd 100644 --- a/p2p/base/turnport_unittest.cc +++ b/p2p/base/turnport_unittest.cc @@ -64,6 +64,8 @@ static const SocketAddress kTurnUdpIntAddr("99.99.99.3", static const SocketAddress kTurnTcpIntAddr("99.99.99.4", cricket::TURN_SERVER_PORT); static const SocketAddress kTurnUdpExtAddr("99.99.99.5", 0); +static const SocketAddress kTurnAlternateUdpIntAddr( + "99.99.99.6", cricket::TURN_SERVER_PORT); static const SocketAddress kTurnUdpIPv6IntAddr( "2400:4030:1:2c00:be30:abcd:efab:cdef", cricket::TURN_SERVER_PORT); static const SocketAddress kTurnUdpIPv6ExtAddr( @@ -208,10 +210,13 @@ class TurnPortTest : public testing::Test, const cricket::ProtocolAddress& server_address) { ASSERT(server_address.proto == cricket::PROTO_UDP); - socket_.reset(socket_factory_.CreateUdpSocket( - rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0)); - ASSERT_TRUE(socket_ != NULL); - socket_->SignalReadPacket.connect(this, &TurnPortTest::OnSocketReadPacket); + if (!socket_) { + socket_.reset(socket_factory_.CreateUdpSocket( + rtc::SocketAddress(kLocalAddr1.ipaddr(), 0), 0, 0)); + ASSERT_TRUE(socket_ != NULL); + socket_->SignalReadPacket.connect( + this, &TurnPortTest::OnSocketReadPacket); + } cricket::RelayCredentials credentials(username, password); turn_port_.reset(cricket::TurnPort::Create( @@ -411,6 +416,80 @@ TEST_F(TurnPortTest, TestTurnAllocateBadPassword) { ASSERT_EQ(0U, turn_port_->Candidates().size()); } +// Tests that a new local address is created after +// STUN_ERROR_ALLOCATION_MISMATCH. +TEST_F(TurnPortTest, TestTurnAllocateMismatch) { + // Do a normal allocation first. + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + // Forces the socket server to assign the same port. + ss_->SetNextPortForTesting(first_addr.port()); + + turn_ready_ = false; + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); +} + +// Tests that a shared-socket-TurnPort creates its own socket after +// STUN_ERROR_ALLOCATION_MISMATCH. +TEST_F(TurnPortTest, TestSharedSocketAllocateMismatch) { + // Do a normal allocation first. + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + turn_ready_ = false; + CreateSharedTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + EXPECT_TRUE(turn_port_->SharedSocket()); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); + EXPECT_FALSE(turn_port_->SharedSocket()); +} + +TEST_F(TurnPortTest, TestTurnTcpAllocateMismatch) { + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + + // Do a normal allocation first. + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + rtc::SocketAddress first_addr(turn_port_->socket()->GetLocalAddress()); + + // Forces the socket server to assign the same port. + ss_->SetNextPortForTesting(first_addr.port()); + + turn_ready_ = false; + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + turn_port_->PrepareAddress(); + + // Verifies that the new port has the same address. + EXPECT_EQ(first_addr, turn_port_->socket()->GetLocalAddress()); + + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + + // Verifies that the new port has a different address now. + EXPECT_NE(first_addr, turn_port_->socket()->GetLocalAddress()); +} + // Do a TURN allocation and try to send a packet to it from the outside. // The packet should be dropped. Then, try to send a packet from TURN to the // outside. It should reach its destination. Finally, try again from the @@ -445,6 +524,103 @@ TEST_F(TurnPortTest, TestTurnTlsTcpConnectionFails) { ASSERT_EQ(0U, turn_port_->Candidates().size()); } +// Test try-alternate-server feature. +TEST_F(TurnPortTest, TestTurnAlternateServer) { + std::vector<rtc::SocketAddress> redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + // Retrieve the address before we run the state machine. + const SocketAddress old_addr = turn_port_->server_address().address; + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_ready_, kTimeout); + // Retrieve the address again, the turn port's address should be + // changed. + const SocketAddress new_addr = turn_port_->server_address().address; + EXPECT_NE(old_addr, new_addr); + ASSERT_EQ(1U, turn_port_->Candidates().size()); + EXPECT_EQ(kTurnUdpExtAddr.ipaddr(), + turn_port_->Candidates()[0].address().ipaddr()); + EXPECT_NE(0, turn_port_->Candidates()[0].address().port()); +} + +// Test that we fail when we redirect to an address different from +// current IP family. +TEST_F(TurnPortTest, TestTurnAlternateServerV4toV6) { + std::vector<rtc::SocketAddress> redirect_addresses; + redirect_addresses.push_back(kTurnUdpIPv6IntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); +} + +// Test that we fail to handle alternate-server response over TCP protocol. +TEST_F(TurnPortTest, TestTurnAlternateServerTcp) { + std::vector<rtc::SocketAddress> redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + turn_server_.set_redirect_hook(&redirector); + turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnTcpProtoAddr); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, cricket::PROTO_TCP); + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); +} + +// Test try-alternate-server catches the case of pingpong. +TEST_F(TurnPortTest, TestTurnAlternateServerPingPong) { + std::vector<rtc::SocketAddress> redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + redirect_addresses.push_back(kTurnUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); + rtc::SocketAddress address; + // Verify that we have exhausted all alternate servers instead of + // failure caused by other errors. + EXPECT_FALSE(redirector.ShouldRedirect(address, &address)); +} + +// Test try-alternate-server catch the case of repeated server. +TEST_F(TurnPortTest, TestTurnAlternateServerDetectRepetition) { + std::vector<rtc::SocketAddress> redirect_addresses; + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + redirect_addresses.push_back(kTurnAlternateUdpIntAddr); + + cricket::TestTurnRedirector redirector(redirect_addresses); + + turn_server_.AddInternalSocket(kTurnAlternateUdpIntAddr, + cricket::PROTO_UDP); + turn_server_.set_redirect_hook(&redirector); + CreateTurnPort(kTurnUsername, kTurnPassword, kTurnUdpProtoAddr); + + turn_port_->PrepareAddress(); + EXPECT_TRUE_WAIT(turn_error_, kTimeout); + ASSERT_EQ(0U, turn_port_->Candidates().size()); +} + + // Run TurnConnectionTest with one-time-use nonce feature. // Here server will send a 438 STALE_NONCE error message for // every TURN transaction. @@ -515,4 +691,3 @@ TEST_F(TurnPortTest, TestResolverShutdown) { EXPECT_EQ(last_fd_count, GetFDCount()); } #endif - diff --git a/p2p/base/turnserver.cc b/p2p/base/turnserver.cc index abc065a..dbcbcd4 100644 --- a/p2p/base/turnserver.cc +++ b/p2p/base/turnserver.cc @@ -62,9 +62,9 @@ inline bool IsTurnChannelData(uint16 msg_type) { return ((msg_type & 0xC000) == 0x4000); } -// IDs used for posted messages. +// IDs used for posted messages for TurnServer::Allocation. enum { - MSG_TIMEOUT, + MSG_ALLOCATION_TIMEOUT, }; // Encapsulates a TURN allocation. @@ -208,6 +208,7 @@ TurnServer::TurnServer(rtc::Thread* thread) : thread_(thread), nonce_key_(rtc::CreateRandomString(kNonceKeySize)), auth_hook_(NULL), + redirect_hook_(NULL), enable_otu_nonce_(false) { } @@ -316,6 +317,15 @@ void TurnServer::HandleStunMessage(Connection* conn, const char* data, return; } + if (redirect_hook_ != NULL && msg.type() == STUN_ALLOCATE_REQUEST) { + rtc::SocketAddress address; + if (redirect_hook_->ShouldRedirect(conn->src(), &address)) { + SendErrorResponseWithAlternateServer( + conn, &msg, address); + return; + } + } + // Look up the key that we'll use to validate the M-I. If we have an // existing allocation, the key will already be cached. Allocation* allocation = FindAllocation(conn); @@ -334,7 +344,6 @@ void TurnServer::HandleStunMessage(Connection* conn, const char* data, } if (!allocation && msg.type() == STUN_ALLOCATE_REQUEST) { - // This is a new allocate request. HandleAllocateRequest(conn, &msg, key); } else if (allocation && (msg.type() != STUN_ALLOCATE_REQUEST || @@ -551,6 +560,17 @@ void TurnServer::SendErrorResponseWithRealmAndNonce( SendStun(conn, &resp); } +void TurnServer::SendErrorResponseWithAlternateServer( + Connection* conn, const StunMessage* msg, + const rtc::SocketAddress& addr) { + TurnMessage resp; + InitErrorResponse(msg, STUN_ERROR_TRY_ALTERNATE, + STUN_ERROR_REASON_TRY_ALTERNATE_SERVER, &resp); + VERIFY(resp.AddAttribute(new StunAddressAttribute( + STUN_ATTR_ALTERNATE_SERVER, addr))); + SendStun(conn, &resp); +} + void TurnServer::SendStun(Connection* conn, StunMessage* msg) { rtc::ByteBuffer buf; // Add a SOFTWARE attribute if one is set. @@ -588,7 +608,9 @@ void TurnServer::DestroyInternalSocket(rtc::AsyncPacketSocket* socket) { InternalSocketMap::iterator iter = server_sockets_.find(socket); if (iter != server_sockets_.end()) { rtc::AsyncPacketSocket* socket = iter->first; - delete socket; + // We must destroy the socket async to avoid invalidating the sigslot + // callback list iterator inside a sigslot callback. + rtc::Thread::Current()->Dispose(socket); server_sockets_.erase(iter); } } @@ -642,7 +664,7 @@ TurnServer::Allocation::~Allocation() { it != perms_.end(); ++it) { delete *it; } - thread_->Clear(this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); LOG_J(LS_INFO, this) << "Allocation destroyed"; } @@ -687,7 +709,7 @@ void TurnServer::Allocation::HandleAllocateRequest(const TurnMessage* msg) { // Figure out the lifetime and start the allocation timer. int lifetime_secs = ComputeLifetime(msg); - thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT); + thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT); LOG_J(LS_INFO, this) << "Created allocation, lifetime=" << lifetime_secs; @@ -714,8 +736,8 @@ void TurnServer::Allocation::HandleRefreshRequest(const TurnMessage* msg) { int lifetime_secs = ComputeLifetime(msg); // Reset the expiration timer. - thread_->Clear(this, MSG_TIMEOUT); - thread_->PostDelayed(lifetime_secs * 1000, this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(lifetime_secs * 1000, this, MSG_ALLOCATION_TIMEOUT); LOG_J(LS_INFO, this) << "Refreshed allocation, lifetime=" << lifetime_secs; @@ -943,7 +965,7 @@ void TurnServer::Allocation::SendExternal(const void* data, size_t size, } void TurnServer::Allocation::OnMessage(rtc::Message* msg) { - ASSERT(msg->message_id == MSG_TIMEOUT); + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); SignalDestroyed(this); delete this; } @@ -968,16 +990,16 @@ TurnServer::Permission::Permission(rtc::Thread* thread, } TurnServer::Permission::~Permission() { - thread_->Clear(this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); } void TurnServer::Permission::Refresh() { - thread_->Clear(this, MSG_TIMEOUT); - thread_->PostDelayed(kPermissionTimeout, this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(kPermissionTimeout, this, MSG_ALLOCATION_TIMEOUT); } void TurnServer::Permission::OnMessage(rtc::Message* msg) { - ASSERT(msg->message_id == MSG_TIMEOUT); + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); SignalDestroyed(this); delete this; } @@ -989,16 +1011,16 @@ TurnServer::Channel::Channel(rtc::Thread* thread, int id, } TurnServer::Channel::~Channel() { - thread_->Clear(this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); } void TurnServer::Channel::Refresh() { - thread_->Clear(this, MSG_TIMEOUT); - thread_->PostDelayed(kChannelTimeout, this, MSG_TIMEOUT); + thread_->Clear(this, MSG_ALLOCATION_TIMEOUT); + thread_->PostDelayed(kChannelTimeout, this, MSG_ALLOCATION_TIMEOUT); } void TurnServer::Channel::OnMessage(rtc::Message* msg) { - ASSERT(msg->message_id == MSG_TIMEOUT); + ASSERT(msg->message_id == MSG_ALLOCATION_TIMEOUT); SignalDestroyed(this); delete this; } diff --git a/p2p/base/turnserver.h b/p2p/base/turnserver.h index 4798232..553d00c 100644 --- a/p2p/base/turnserver.h +++ b/p2p/base/turnserver.h @@ -63,6 +63,14 @@ class TurnAuthInterface { std::string* key) = 0; }; +// An interface enables Turn Server to control redirection behavior. +class TurnRedirectInterface { + public: + virtual bool ShouldRedirect(const rtc::SocketAddress& address, + rtc::SocketAddress* out) = 0; + virtual ~TurnRedirectInterface() {} +}; + // The core TURN server class. Give it a socket to listen on via // AddInternalServerSocket, and a factory to create external sockets via // SetExternalSocketFactory, and it's ready to go. @@ -83,6 +91,10 @@ class TurnServer : public sigslot::has_slots<> { // Sets the authentication callback; does not take ownership. void set_auth_hook(TurnAuthInterface* auth_hook) { auth_hook_ = auth_hook; } + void set_redirect_hook(TurnRedirectInterface* redirect_hook) { + redirect_hook_ = redirect_hook; + } + void set_enable_otu_nonce(bool enable) { enable_otu_nonce_ = enable; } // Starts listening for packets from internal clients. @@ -155,6 +167,11 @@ class TurnServer : public sigslot::has_slots<> { const StunMessage* req, int code, const std::string& reason); + + void SendErrorResponseWithAlternateServer(Connection* conn, + const StunMessage* req, + const rtc::SocketAddress& addr); + void SendStun(Connection* conn, StunMessage* msg); void Send(Connection* conn, const rtc::ByteBuffer& buf); @@ -171,14 +188,17 @@ class TurnServer : public sigslot::has_slots<> { std::string realm_; std::string software_; TurnAuthInterface* auth_hook_; + TurnRedirectInterface* redirect_hook_; // otu - one-time-use. Server will respond with 438 if it's // sees the same nonce in next transaction. bool enable_otu_nonce_; + InternalSocketMap server_sockets_; ServerSocketMap server_listen_sockets_; rtc::scoped_ptr<rtc::PacketSocketFactory> external_socket_factory_; rtc::SocketAddress external_addr_; + AllocationMap allocations_; }; diff --git a/p2p/client/autoportallocator.h b/p2p/client/autoportallocator.h index 298f829..ed87162 100644 --- a/p2p/client/autoportallocator.h +++ b/p2p/client/autoportallocator.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_ -#define TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_ +#ifndef TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ +#define TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ #include <string> #include <vector> @@ -66,4 +66,4 @@ class AutoPortAllocator : public cricket::HttpPortAllocator { } }; -#endif // TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_ +#endif // TALK_P2P_CLIENT_AUTOPORTALLOCATOR_H_ diff --git a/p2p/client/basicportallocator.cc b/p2p/client/basicportallocator.cc index d39db8f..0ec13e7 100644 --- a/p2p/client/basicportallocator.cc +++ b/p2p/client/basicportallocator.cc @@ -47,13 +47,15 @@ using rtc::CreateRandomString; namespace { -const uint32 MSG_CONFIG_START = 1; -const uint32 MSG_CONFIG_READY = 2; -const uint32 MSG_ALLOCATE = 3; -const uint32 MSG_ALLOCATION_PHASE = 4; -const uint32 MSG_SHAKE = 5; -const uint32 MSG_SEQUENCEOBJECTS_CREATED = 6; -const uint32 MSG_CONFIG_STOP = 7; +enum { + MSG_CONFIG_START, + MSG_CONFIG_READY, + MSG_ALLOCATE, + MSG_ALLOCATION_PHASE, + MSG_SHAKE, + MSG_SEQUENCEOBJECTS_CREATED, + MSG_CONFIG_STOP, +}; const int PHASE_UDP = 0; const int PHASE_RELAY = 1; @@ -147,9 +149,6 @@ class AllocationSequence : public rtc::MessageHandler, const rtc::PacketTime& packet_time); void OnPortDestroyed(PortInterface* port); - void OnResolvedTurnServerAddress( - TurnPort* port, const rtc::SocketAddress& server_address, - const rtc::SocketAddress& resolved_server_address); BasicPortAllocatorSession* session_; rtc::Network* network_; @@ -161,8 +160,7 @@ class AllocationSequence : public rtc::MessageHandler, rtc::scoped_ptr<rtc::AsyncPacketSocket> udp_socket_; // There will be only one udp port per AllocationSequence. UDPPort* udp_port_; - // Keeping a map for turn ports keyed with server addresses. - std::map<rtc::SocketAddress, Port*> turn_ports_; + std::vector<TurnPort*> turn_ports_; int phase_; }; @@ -228,10 +226,11 @@ BasicPortAllocator::~BasicPortAllocator() { PortAllocatorSession *BasicPortAllocator::CreateSessionInternal( const std::string& content_name, int component, const std::string& ice_ufrag, const std::string& ice_pwd) { - return new BasicPortAllocatorSession(this, content_name, component, - ice_ufrag, ice_pwd); + return new BasicPortAllocatorSession( + this, content_name, component, ice_ufrag, ice_pwd); } + // BasicPortAllocatorSession BasicPortAllocatorSession::BasicPortAllocatorSession( BasicPortAllocator *allocator, @@ -432,7 +431,11 @@ void BasicPortAllocatorSession::DoAllocate() { } if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6) && +#ifdef USE_WEBRTC_DEV_BRANCH + networks[i]->GetBestIP().family() == AF_INET6) { +#else // USE_WEBRTC_DEV_BRANCH networks[i]->ip().family() == AF_INET6) { +#endif // USE_WEBRTC_DEV_BRANCH // Skip IPv6 networks unless the flag's been set. continue; } @@ -530,8 +533,10 @@ void BasicPortAllocatorSession::OnCandidateReady( // Send candidates whose protocol is enabled. std::vector<Candidate> candidates; ProtocolType pvalue; + bool candidate_allowed_to_send = CheckCandidateFilter(c); if (StringToProto(c.protocol().c_str(), &pvalue) && - data->sequence()->ProtocolEnabled(pvalue)) { + data->sequence()->ProtocolEnabled(pvalue) && + candidate_allowed_to_send) { candidates.push_back(c); } @@ -542,7 +547,9 @@ void BasicPortAllocatorSession::OnCandidateReady( // Moving to READY state as we have atleast one candidate from the port. // Since this port has atleast one candidate we should forward this port // to listners, to allow connections from this port. - if (!data->ready()) { + // Also we should make sure that candidate gathered from this port is allowed + // to send outside. + if (!data->ready() && candidate_allowed_to_send) { data->set_ready(); SignalPortReady(this, port); } @@ -588,6 +595,8 @@ void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence* seq, const std::vector<Candidate>& potentials = it->port()->Candidates(); for (size_t i = 0; i < potentials.size(); ++i) { + if (!CheckCandidateFilter(potentials[i])) + continue; ProtocolType pvalue; if (!StringToProto(potentials[i].protocol().c_str(), &pvalue)) continue; @@ -602,6 +611,31 @@ void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence* seq, } } +bool BasicPortAllocatorSession::CheckCandidateFilter(const Candidate& c) { + uint32 filter = allocator_->candidate_filter(); + bool allowed = false; + if (filter & CF_RELAY) { + allowed |= (c.type() == RELAY_PORT_TYPE); + } + + if (filter & CF_REFLEXIVE) { + // We allow host candidates if the filter allows server-reflexive candidates + // and the candidate is a public IP. Because we don't generate + // server-reflexive candidates if they have the same IP as the host + // candidate (i.e. when the host candidate is a public IP), filtering to + // only server-reflexive candidates won't work right when the host + // candidates have public IPs. + allowed |= (c.type() == STUN_PORT_TYPE) || + (c.type() == LOCAL_PORT_TYPE && !c.address().IsPrivateIP()); + } + + if (filter & CF_HOST) { + allowed |= (c.type() == LOCAL_PORT_TYPE); + } + + return allowed; +} + void BasicPortAllocatorSession::OnPortAllocationComplete( AllocationSequence* seq) { // Send candidate allocation complete signal if all ports are done. @@ -698,7 +732,12 @@ AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session, uint32 flags) : session_(session), network_(network), + +#ifdef USE_WEBRTC_DEV_BRANCH + ip_(network->GetBestIP()), +#else // USE_WEBRTC_DEV_BRANCH ip_(network->ip()), +#endif // USE_WEBRTC_DEV_BRANCH config_(config), state_(kInit), flags_(flags), @@ -741,7 +780,11 @@ AllocationSequence::~AllocationSequence() { void AllocationSequence::DisableEquivalentPhases(rtc::Network* network, PortConfiguration* config, uint32* flags) { +#ifdef USE_WEBRTC_DEV_BRANCH + if (!((network == network_) && (ip_ == network->GetBestIP()))) { +#else // USE_WEBRTC_DEV_BRANCH if (!((network == network_) && (ip_ == network->ip()))) { +#endif // USE_WEBRTC_DEV_BRANCH // Different network setup; nothing is equivalent. return; } @@ -1020,26 +1063,15 @@ void AllocationSequence::CreateTurnPort(const RelayServerConfig& config) { // don't pass shared socket for ports which will create TCP sockets. // TODO(mallinath) - Enable shared socket mode for TURN ports. Disabled // due to webrtc bug https://code.google.com/p/webrtc/issues/detail?id=3537 - if (IsFlagSet(PORTALLOCATOR_ENABLE_TURN_SHARED_SOCKET) && + if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && relay_port->proto == PROTO_UDP) { port = TurnPort::Create(session_->network_thread(), session_->socket_factory(), network_, udp_socket_.get(), session_->username(), session_->password(), *relay_port, config.credentials, config.priority); - // If we are using shared socket for TURN and udp ports, we need to - // find a way to demux the packets to the correct port when received. - // Mapping against server_address is one way of doing this. When packet - // is received the remote_address will be checked against the map. - // If server address is not resolved, a signal will be sent from the port - // after the address is resolved. The map entry will updated with the - // resolved address when the signal is received from the port. - if ((*relay_port).address.IsUnresolved()) { - // If server address is not resolved then listen for signal from port. - port->SignalResolvedServerAddress.connect( - this, &AllocationSequence::OnResolvedTurnServerAddress); - } - turn_ports_[(*relay_port).address] = port; + + turn_ports_.push_back(port); // Listen to the port destroyed signal, to allow AllocationSequence to // remove entrt from it's map. port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed); @@ -1063,51 +1095,45 @@ void AllocationSequence::OnReadPacket( const rtc::SocketAddress& remote_addr, const rtc::PacketTime& packet_time) { ASSERT(socket == udp_socket_.get()); - // If the packet is received from one of the TURN server in the config, then - // pass down the packet to that port, otherwise it will be handed down to - // the local udp port. - Port* port = NULL; - std::map<rtc::SocketAddress, Port*>::iterator iter = - turn_ports_.find(remote_addr); - if (iter != turn_ports_.end()) { - port = iter->second; - } else if (udp_port_) { - port = udp_port_; + + bool turn_port_found = false; + + // Try to find the TurnPort that matches the remote address. Note that the + // message could be a STUN binding response if the TURN server is also used as + // a STUN server. We don't want to parse every message here to check if it is + // a STUN binding response, so we pass the message to TurnPort regardless of + // the message type. The TurnPort will just ignore the message since it will + // not find any request by transaction ID. + for (std::vector<TurnPort*>::const_iterator it = turn_ports_.begin(); + it != turn_ports_.end(); ++it) { + TurnPort* port = *it; + if (port->server_address().address == remote_addr) { + port->HandleIncomingPacket(socket, data, size, remote_addr, packet_time); + turn_port_found = true; + break; + } } - ASSERT(port != NULL); - if (port) { - port->HandleIncomingPacket(socket, data, size, remote_addr, packet_time); + + if (udp_port_) { + const ServerAddresses& stun_servers = udp_port_->server_addresses(); + + // Pass the packet to the UdpPort if there is no matching TurnPort, or if + // the TURN server is also a STUN server. + if (!turn_port_found || + stun_servers.find(remote_addr) != stun_servers.end()) { + udp_port_->HandleIncomingPacket( + socket, data, size, remote_addr, packet_time); + } } } void AllocationSequence::OnPortDestroyed(PortInterface* port) { if (udp_port_ == port) { udp_port_ = NULL; - } else { - std::map<rtc::SocketAddress, Port*>::iterator iter; - for (iter = turn_ports_.begin(); iter != turn_ports_.end(); ++iter) { - if (iter->second == port) { - turn_ports_.erase(iter); - break; - } - } - } -} - -void AllocationSequence::OnResolvedTurnServerAddress( - TurnPort* port, const rtc::SocketAddress& server_address, - const rtc::SocketAddress& resolved_server_address) { - std::map<rtc::SocketAddress, Port*>::iterator iter; - iter = turn_ports_.find(server_address); - if (iter == turn_ports_.end()) { - LOG(LS_INFO) << "TurnPort entry is not found in the map."; return; } - ASSERT(iter->second == port); - // Remove old entry and then insert using the resolved address as key. - turn_ports_.erase(iter); - turn_ports_[resolved_server_address] = port; + turn_ports_.erase(std::find(turn_ports_.begin(), turn_ports_.end(), port)); } // PortConfiguration diff --git a/p2p/client/basicportallocator.h b/p2p/client/basicportallocator.h index 5f43880..d424772 100644 --- a/p2p/client/basicportallocator.h +++ b/p2p/client/basicportallocator.h @@ -160,7 +160,6 @@ class BasicPortAllocatorSession : public PortAllocatorSession, void set_ready() { ASSERT(state_ == STATE_INIT); state_ = STATE_READY; } void set_complete() { - ASSERT(state_ == STATE_READY); state_ = STATE_COMPLETE; } void set_error() { @@ -201,6 +200,8 @@ class BasicPortAllocatorSession : public PortAllocatorSession, void OnPortAllocationComplete(AllocationSequence* seq); PortData* FindPort(Port* port); + bool CheckCandidateFilter(const Candidate& c); + BasicPortAllocator* allocator_; rtc::Thread* network_thread_; rtc::scoped_ptr<rtc::PacketSocketFactory> owned_socket_factory_; diff --git a/p2p/client/connectivitychecker.cc b/p2p/client/connectivitychecker.cc index 06de5e4..723c5a1 100644 --- a/p2p/client/connectivitychecker.cc +++ b/p2p/client/connectivitychecker.cc @@ -1,5 +1,29 @@ -// Copyright 2011 Google Inc. All Rights Reserved. - +/* + * libjingle + * Copyright 2011, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #include <string> @@ -214,7 +238,12 @@ void ConnectivityChecker::OnRequestDone(rtc::AsyncHttpRequest* request) { return; } rtc::ProxyInfo proxy_info = request->proxy(); - NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address)); + NicMap::iterator i = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH if (i != nics_.end()) { int port = request->port(); uint32 now = rtc::Time(); @@ -247,7 +276,11 @@ void ConnectivityChecker::OnRelayPortComplete(Port* port) { ASSERT(worker_ == rtc::Thread::Current()); RelayPort* relay_port = reinterpret_cast<RelayPort*>(port); const ProtocolAddress* address = relay_port->ServerAddress(0); +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); if (i != nics_.end()) { // We have it already, add the new information. @@ -281,7 +314,11 @@ void ConnectivityChecker::OnStunPortComplete(Port* port) { ASSERT(worker_ == rtc::Thread::Current()); const std::vector<Candidate> candidates = port->Candidates(); Candidate c = candidates[0]; +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); if (i != nics_.end()) { // We have it already, add the new information. @@ -300,7 +337,11 @@ void ConnectivityChecker::OnStunPortComplete(Port* port) { void ConnectivityChecker::OnStunPortError(Port* port) { ASSERT(worker_ == rtc::Thread::Current()); LOG(LS_ERROR) << "Stun address error."; +#ifdef USE_WEBRTC_DEV_BRANCH + rtc::IPAddress ip = port->Network()->GetBestIP(); +#else // USE_WEBRTC_DEV_BRANCH rtc::IPAddress ip = port->Network()->ip(); +#endif // USE_WEBRTC_DEV_BRANCH NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address)); if (i != nics_.end()) { // We have it already, add the new information. @@ -337,19 +378,36 @@ HttpPortAllocator* ConnectivityChecker::CreatePortAllocator( StunPort* ConnectivityChecker::CreateStunPort( const std::string& username, const std::string& password, const PortConfiguration* config, rtc::Network* network) { - return StunPort::Create(worker_, socket_factory_.get(), - network, network->ip(), 0, 0, - username, password, config->stun_servers); + return StunPort::Create(worker_, + socket_factory_.get(), + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + 0, + 0, + username, + password, + config->stun_servers); } RelayPort* ConnectivityChecker::CreateRelayPort( const std::string& username, const std::string& password, const PortConfiguration* config, rtc::Network* network) { - return RelayPort::Create(worker_, socket_factory_.get(), - network, network->ip(), + return RelayPort::Create(worker_, + socket_factory_.get(), + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH port_allocator_->min_port(), port_allocator_->max_port(), - username, password); + username, + password); } void ConnectivityChecker::CreateRelayPorts( @@ -365,8 +423,12 @@ void ConnectivityChecker::CreateRelayPorts( for (relay = config->relays.begin(); relay != config->relays.end(); ++relay) { for (uint32 i = 0; i < networks.size(); ++i) { - NicMap::iterator iter = nics_.find(NicId(networks[i]->ip(), - proxy_info.address)); + NicMap::iterator iter = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[i]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[i]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH if (iter != nics_.end()) { // TODO: Now setting the same start time for all protocols. // This might affect accuracy, but since we are mainly looking for @@ -423,7 +485,11 @@ void ConnectivityChecker::AllocatePorts() { rtc::ProxyInfo proxy_info = GetProxyInfo(); bool allocate_relay_ports = false; for (uint32 i = 0; i < networks.size(); ++i) { +#ifdef USE_WEBRTC_DEV_BRANCH + if (AddNic(networks[i]->GetBestIP(), proxy_info.address)) { +#else // USE_WEBRTC_DEV_BRANCH if (AddNic(networks[i]->ip(), proxy_info.address)) { +#endif // USE_WEBRTC_DEV_BRANCH Port* port = CreateStunPort(username, password, &config, networks[i]); if (port) { @@ -500,7 +566,12 @@ void ConnectivityChecker::RegisterHttpStart(int port) { return; } rtc::ProxyInfo proxy_info = GetProxyInfo(); - NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address)); + NicMap::iterator i = +#ifdef USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->GetBestIP(), proxy_info.address)); +#else // USE_WEBRTC_DEV_BRANCH + nics_.find(NicId(networks[0]->ip(), proxy_info.address)); +#endif // USE_WEBRTC_DEV_BRANCH if (i != nics_.end()) { uint32 now = rtc::Time(); NicInfo* nic_info = &i->second; diff --git a/p2p/client/connectivitychecker_unittest.cc b/p2p/client/connectivitychecker_unittest.cc index b96cf17..187505a 100644 --- a/p2p/client/connectivitychecker_unittest.cc +++ b/p2p/client/connectivitychecker_unittest.cc @@ -1,5 +1,29 @@ -// Copyright 2011 Google Inc. All Rights Reserved. - +/* + * libjingle + * Copyright 2011, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #include <string> @@ -211,19 +235,35 @@ class ConnectivityCheckerForTest : public ConnectivityChecker { virtual StunPort* CreateStunPort( const std::string& username, const std::string& password, const PortConfiguration* config, rtc::Network* network) { - return new FakeStunPort(worker(), socket_factory_, - network, network->ip(), - kMinPort, kMaxPort, - username, password, + return new FakeStunPort(worker(), + socket_factory_, + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + kMinPort, + kMaxPort, + username, + password, config->stun_servers); } virtual RelayPort* CreateRelayPort( const std::string& username, const std::string& password, const PortConfiguration* config, rtc::Network* network) { - return new FakeRelayPort(worker(), socket_factory_, - network, network->ip(), - kMinPort, kMaxPort, - username, password); + return new FakeRelayPort(worker(), + socket_factory_, + network, +#ifdef USE_WEBRTC_DEV_BRANCH + network->GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network->ip(), +#endif // USE_WEBRTC_DEV_BRANCH + kMinPort, + kMaxPort, + username, + password); } virtual void InitiateProxyDetection() { if (!proxy_initiated_) { diff --git a/p2p/client/fakeportallocator.h b/p2p/client/fakeportallocator.h index 6c36c4e..e1a04dd 100644 --- a/p2p/client/fakeportallocator.h +++ b/p2p/client/fakeportallocator.h @@ -1,6 +1,29 @@ -// Copyright 2010 Google Inc. All Rights Reserved, -// -// Author: Justin Uberti (juberti@google.com) +/* + * libjingle + * Copyright 2010, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #ifndef TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_ #define TALK_P2P_CLIENT_FAKEPORTALLOCATOR_H_ @@ -39,10 +62,18 @@ class FakePortAllocatorSession : public PortAllocatorSession { virtual void StartGettingPorts() { if (!port_) { - port_.reset(cricket::UDPPort::Create(worker_thread_, factory_, - &network_, network_.ip(), 0, 0, - username(), - password())); + port_.reset(cricket::UDPPort::Create(worker_thread_, + factory_, + &network_, +#ifdef USE_WEBRTC_DEV_BRANCH + network_.GetBestIP(), +#else // USE_WEBRTC_DEV_BRANCH + network_.ip(), +#endif // USE_WEBRTC_DEV_BRANCH + 0, + 0, + username(), + password())); AddPort(port_.get()); } ++port_config_count_; diff --git a/p2p/client/portallocator_unittest.cc b/p2p/client/portallocator_unittest.cc index 57cfbe3..e793064 100644 --- a/p2p/client/portallocator_unittest.cc +++ b/p2p/client/portallocator_unittest.cc @@ -53,6 +53,7 @@ using rtc::SocketAddress; using rtc::Thread; static const SocketAddress kClientAddr("11.11.11.11", 0); +static const SocketAddress kPrivateAddr("192.168.1.11", 0); static const SocketAddress kClientIPv6Addr( "2401:fa00:4:1000:be30:5bff:fee5:c3", 0); static const SocketAddress kClientAddr2("22.22.22.22", 0); @@ -132,9 +133,32 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { bool SetPortRange(int min_port, int max_port) { return allocator_->SetPortRange(min_port, max_port); } - rtc::NATServer* CreateNatServer(const SocketAddress& addr, - rtc::NATType type) { - return new rtc::NATServer(type, vss_.get(), addr, vss_.get(), addr); + void ResetWithNatServer(const rtc::SocketAddress& stun_server) { + nat_server_.reset(new rtc::NATServer( + rtc::NAT_OPEN_CONE, vss_.get(), kNatAddr, vss_.get(), kNatAddr)); + + ServerAddresses stun_servers; + stun_servers.insert(stun_server); + allocator_.reset(new cricket::BasicPortAllocator( + &network_manager_, &nat_socket_factory_, stun_servers)); + allocator().set_step_delay(cricket::kMinimumStepDelay); + } + + void AddTurnServers(const rtc::SocketAddress& udp_turn, + const rtc::SocketAddress& tcp_turn) { + cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); + cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); + relay_server.credentials = credentials; + + if (!udp_turn.IsNil()) { + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnUdpIntAddr, cricket::PROTO_UDP, false)); + } + if (!tcp_turn.IsNil()) { + relay_server.ports.push_back(cricket::ProtocolAddress( + kTurnTcpIntAddr, cricket::PROTO_TCP, false)); + } + allocator_->AddRelay(relay_server); } bool CreateSession(int component) { @@ -253,6 +277,7 @@ class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> { rtc::scoped_ptr<rtc::VirtualSocketServer> vss_; rtc::scoped_ptr<rtc::FirewallSocketServer> fss_; rtc::SocketServerScope ss_scope_; + rtc::scoped_ptr<rtc::NATServer> nat_server_; rtc::NATSocketFactory nat_factory_; rtc::BasicPacketSocketFactory nat_socket_factory_; cricket::TestStunServer stun_server_; @@ -538,7 +563,7 @@ TEST_F(PortAllocatorTest, TestCandidatePriorityOfMultipleInterfaces) { // Test to verify ICE restart process. TEST_F(PortAllocatorTest, TestGetAllPortsRestarts) { AddInterface(kClientAddr); - EXPECT_TRUE(CreateSession(1)); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); session_->StartGettingPorts(); EXPECT_EQ_WAIT(7U, candidates_.size(), kDefaultAllocationTimeout); EXPECT_EQ(4U, ports_.size()); @@ -546,6 +571,73 @@ TEST_F(PortAllocatorTest, TestGetAllPortsRestarts) { // TODO - Extend this to verify ICE restart. } +// Test ICE candidate filter mechanism with options Relay/Host/Reflexive. +TEST_F(PortAllocatorTest, TestCandidateFilterWithRelayOnly) { + AddInterface(kClientAddr); + allocator().set_candidate_filter(cricket::CF_RELAY); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + // Using GTURN, we will have 4 candidates. + EXPECT_EQ(4U, candidates_.size()); + EXPECT_EQ(1U, ports_.size()); // Only Relay port will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::RELAY_PORT_TYPE), candidates_[i].type()); + } +} + +TEST_F(PortAllocatorTest, TestCandidateFilterWithHostOnly) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_HOST); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(2U, candidates_.size()); // Host UDP/TCP candidates only. + EXPECT_EQ(2U, ports_.size()); // UDP/TCP ports only. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::LOCAL_PORT_TYPE), candidates_[i].type()); + } +} + +// Host is behind the NAT. +TEST_F(PortAllocatorTest, TestCandidateFilterWithReflexiveOnly) { + AddInterface(kPrivateAddr); + ResetWithNatServer(kStunAddr); + + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_REFLEXIVE); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + // Host is behind NAT, no private address will be exposed. Hence only UDP + // port with STUN candidate will be sent outside. + EXPECT_EQ(1U, candidates_.size()); // Only STUN candidate. + EXPECT_EQ(1U, ports_.size()); // Only UDP port will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::STUN_PORT_TYPE), candidates_[i].type()); + } +} + +// Host is not behind the NAT. +TEST_F(PortAllocatorTest, TestCandidateFilterWithReflexiveOnlyAndNoNAT) { + AddInterface(kClientAddr); + allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); + allocator().set_candidate_filter(cricket::CF_REFLEXIVE); + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + // Host has a public address, both UDP and TCP candidates will be exposed. + EXPECT_EQ(2U, candidates_.size()); // Local UDP + TCP candidate. + EXPECT_EQ(2U, ports_.size()); // UDP and TCP ports will be in ready state. + for (size_t i = 0; i < candidates_.size(); ++i) { + EXPECT_EQ(std::string(cricket::LOCAL_PORT_TYPE), candidates_[i].type()); + } +} + TEST_F(PortAllocatorTest, TestBasicMuxFeatures) { AddInterface(kClientAddr); allocator().set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE); @@ -698,13 +790,8 @@ TEST_F(PortAllocatorTest, TestSharedSocketWithoutNat) { // local candidates as client behind a nat. TEST_F(PortAllocatorTest, TestSharedSocketWithNat) { AddInterface(kClientAddr); - rtc::scoped_ptr<rtc::NATServer> nat_server( - CreateNatServer(kNatAddr, rtc::NAT_OPEN_CONE)); - ServerAddresses stun_servers; - stun_servers.insert(kStunAddr); - allocator_.reset(new cricket::BasicPortAllocator( - &network_manager_, &nat_socket_factory_, stun_servers)); - allocator_->set_step_delay(cricket::kMinimumStepDelay); + ResetWithNatServer(kStunAddr); + allocator_->set_flags(allocator().flags() | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET); @@ -726,14 +813,8 @@ TEST_F(PortAllocatorTest, TestSharedSocketWithoutNatUsingTurn) { turn_server_.AddInternalSocket(kTurnTcpIntAddr, cricket::PROTO_TCP); AddInterface(kClientAddr); allocator_.reset(new cricket::BasicPortAllocator(&network_manager_)); - cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); - cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); - relay_server.credentials = credentials; - relay_server.ports.push_back(cricket::ProtocolAddress( - kTurnUdpIntAddr, cricket::PROTO_UDP, false)); - relay_server.ports.push_back(cricket::ProtocolAddress( - kTurnTcpIntAddr, cricket::PROTO_TCP, false)); - allocator_->AddRelay(relay_server); + + AddTurnServers(kTurnUdpIntAddr, kTurnTcpIntAddr); allocator_->set_step_delay(cricket::kMinimumStepDelay); allocator_->set_flags(allocator().flags() | @@ -790,20 +871,10 @@ TEST_F(PortAllocatorTest, TestSharedSocketWithServerAddressResolve) { // stun and turn candidates. TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurn) { AddInterface(kClientAddr); - rtc::scoped_ptr<rtc::NATServer> nat_server( - CreateNatServer(kNatAddr, rtc::NAT_OPEN_CONE)); - ServerAddresses stun_servers; - stun_servers.insert(kStunAddr); - allocator_.reset(new cricket::BasicPortAllocator( - &network_manager_, &nat_socket_factory_, stun_servers)); - cricket::RelayServerConfig relay_server(cricket::RELAY_TURN); - cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); - relay_server.credentials = credentials; - relay_server.ports.push_back(cricket::ProtocolAddress( - kTurnUdpIntAddr, cricket::PROTO_UDP, false)); - allocator_->AddRelay(relay_server); + ResetWithNatServer(kStunAddr); + + AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); - allocator_->set_step_delay(cricket::kMinimumStepDelay); allocator_->set_flags(allocator().flags() | cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | @@ -829,6 +900,45 @@ TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurn) { EXPECT_EQ(1U, ports_[1]->Candidates().size()); } +// Test that when PORTALLOCATOR_ENABLE_SHARED_SOCKET is enabled and the TURN +// server is also used as the STUN server, we should get 'local', 'stun', and +// 'relay' candidates. +TEST_F(PortAllocatorTest, TestSharedSocketWithNatUsingTurnAsStun) { + AddInterface(kClientAddr); + ResetWithNatServer(kTurnUdpIntAddr); + AddTurnServers(kTurnUdpIntAddr, rtc::SocketAddress()); + + // Must set the step delay to 0 to make sure the relay allocation phase is + // started before the STUN candidates are obtained, so that the STUN binding + // response is processed when both StunPort and TurnPort exist to reproduce + // webrtc issue 3537. + allocator_->set_step_delay(0); + allocator_->set_flags(allocator().flags() | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | + cricket::PORTALLOCATOR_DISABLE_TCP); + + EXPECT_TRUE(CreateSession(cricket::ICE_CANDIDATE_COMPONENT_RTP)); + session_->StartGettingPorts(); + + ASSERT_EQ_WAIT(3U, candidates_.size(), kDefaultAllocationTimeout); + EXPECT_PRED5(CheckCandidate, candidates_[0], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "local", "udp", kClientAddr); + EXPECT_PRED5(CheckCandidate, candidates_[1], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "stun", "udp", + rtc::SocketAddress(kNatAddr.ipaddr(), 0)); + EXPECT_PRED5(CheckCandidate, candidates_[2], + cricket::ICE_CANDIDATE_COMPONENT_RTP, "relay", "udp", + rtc::SocketAddress(kTurnUdpExtAddr.ipaddr(), 0)); + EXPECT_EQ(candidates_[2].related_address(), candidates_[1].address()); + + EXPECT_TRUE_WAIT(candidate_allocation_done_, kDefaultAllocationTimeout); + EXPECT_EQ(3U, candidates_.size()); + // Local port will be created first and then TURN port. + EXPECT_EQ(2U, ports_[0]->Candidates().size()); + EXPECT_EQ(1U, ports_[1]->Candidates().size()); +} + // This test verifies when PORTALLOCATOR_ENABLE_SHARED_SOCKET flag is enabled // and fail to generate STUN candidate, local UDP candidate is generated // properly. diff --git a/p2p/client/sessionmanagertask.h b/p2p/client/sessionmanagertask.h index d7d9733..e16d9d6 100644 --- a/p2p/client/sessionmanagertask.h +++ b/p2p/client/sessionmanagertask.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _SESSIONMANAGERTASK_H_ -#define _SESSIONMANAGERTASK_H_ +#ifndef TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ +#define TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ #include "talk/p2p/base/sessionmanager.h" #include "talk/p2p/client/sessionsendtask.h" @@ -90,4 +90,4 @@ class SessionManagerTask : public buzz::XmppTask { } // namespace cricket -#endif // _SESSIONMANAGERTASK_H_ +#endif // TALK_P2P_CLIENT_SESSIONMANAGERTASK_H_ diff --git a/session/media/call.cc b/session/media/call.cc index 56d886d..4fbdd0f 100644 --- a/session/media/call.cc +++ b/session/media/call.cc @@ -94,7 +94,6 @@ void AudioSourceProxy::OnMediaStreamsUpdate(Call* call, Session* session, Call::Call(MediaSessionClient* session_client) : id_(rtc::CreateRandomId()), session_client_(session_client), - local_renderer_(NULL), has_video_(false), has_data_(false), muted_(false), @@ -207,13 +206,6 @@ bool Call::SendViewRequest(Session* session, return session->SendInfoMessage(elems, session->remote_name()); } -void Call::SetLocalRenderer(VideoRenderer* renderer) { - local_renderer_ = renderer; - if (session_client_->GetFocus() == this) { - session_client_->channel_manager()->SetLocalRenderer(renderer); - } -} - void Call::SetVideoRenderer(Session* session, uint32 ssrc, VideoRenderer* renderer) { VideoChannel* video_channel = GetVideoChannel(session); @@ -418,8 +410,6 @@ void Call::EnableChannels(bool enable) { for (it = media_session_map_.begin(); it != media_session_map_.end(); ++it) { EnableSessionChannels(it->second.session, enable); } - session_client_->channel_manager()->SetLocalRenderer( - (enable) ? local_renderer_ : NULL); } void Call::EnableSessionChannels(Session* session, bool enable) { diff --git a/session/media/call.h b/session/media/call.h index a81e74a..be9397e 100644 --- a/session/media/call.h +++ b/session/media/call.h @@ -96,7 +96,6 @@ class Call : public rtc::MessageHandler, public sigslot::has_slots<> { void Terminate(); bool SendViewRequest(Session* session, const ViewRequest& view_request); - void SetLocalRenderer(VideoRenderer* renderer); void SetVideoRenderer(Session* session, uint32 ssrc, VideoRenderer* renderer); void StartConnectionMonitor(Session* session, int cms); @@ -284,7 +283,6 @@ class Call : public rtc::MessageHandler, public sigslot::has_slots<> { MediaSessionMap media_session_map_; std::map<std::string, CurrentSpeakerMonitor*> speaker_monitor_map_; - VideoRenderer* local_renderer_; bool has_video_; bool has_data_; bool muted_; diff --git a/session/media/channelmanager.cc b/session/media/channelmanager.cc index a1cdcc0..199bc86 100644 --- a/session/media/channelmanager.cc +++ b/session/media/channelmanager.cc @@ -137,6 +137,12 @@ void ChannelManager::Construct(MediaEngineInterface* me, this, &ChannelManager::OnVideoCaptureStateChange); capture_manager_->SignalCapturerStateChange.connect( this, &ChannelManager::OnVideoCaptureStateChange); + + if (worker_thread_ != rtc::Thread::Current()) { + // Do not allow invoking calls to other threads on the worker thread. + worker_thread_->Invoke<bool>( + rtc::Bind(&rtc::Thread::SetAllowBlockingCalls, worker_thread_, false)); + } } ChannelManager::~ChannelManager() { @@ -277,10 +283,6 @@ bool ChannelManager::Init() { if (default_video_encoder_config_.max_codec.id != 0) { SetDefaultVideoEncoderConfig(default_video_encoder_config_); } - // And the local renderer. - if (local_renderer_) { - SetLocalRenderer(local_renderer_); - } } } return initialized_; @@ -750,19 +752,6 @@ bool ChannelManager::SetLocalMonitor(bool enable) { return ret; } -bool ChannelManager::SetLocalRenderer(VideoRenderer* renderer) { - bool ret = true; - if (initialized_) { - ret = worker_thread_->Invoke<bool>( - Bind(&MediaEngineInterface::SetLocalRenderer, - media_engine_.get(), renderer)); - } - if (ret) { - local_renderer_ = renderer; - } - return ret; -} - void ChannelManager::SetVoiceLogging(int level, const char* filter) { if (initialized_) { worker_thread_->Invoke<void>( diff --git a/session/media/channelmanager.h b/session/media/channelmanager.h index f7a2890..fa79014 100644 --- a/session/media/channelmanager.h +++ b/session/media/channelmanager.h @@ -170,8 +170,6 @@ class ChannelManager : public rtc::MessageHandler, // Starts/stops the local microphone and enables polling of the input level. bool SetLocalMonitor(bool enable); bool monitoring() const { return monitoring_; } - // Sets the local renderer where to renderer the local camera. - bool SetLocalRenderer(VideoRenderer* renderer); bool capturing() const { return capturing_; } // Configures the logging output of the mediaengine(s). diff --git a/session/media/channelmanager_unittest.cc b/session/media/channelmanager_unittest.cc index f234732..98f1400 100644 --- a/session/media/channelmanager_unittest.cc +++ b/session/media/channelmanager_unittest.cc @@ -509,22 +509,6 @@ TEST_F(ChannelManagerTest, GetSetOutputVolume) { EXPECT_EQ(60, level); } -// Test that a value set before Init is applied properly. -TEST_F(ChannelManagerTest, SetLocalRendererBeforeInit) { - cricket::NullVideoRenderer renderer; - EXPECT_TRUE(cm_->SetLocalRenderer(&renderer)); - EXPECT_TRUE(cm_->Init()); - EXPECT_EQ(&renderer, fme_->local_renderer()); -} - -// Test that a value set after init is passed through properly. -TEST_F(ChannelManagerTest, SetLocalRenderer) { - cricket::NullVideoRenderer renderer; - EXPECT_TRUE(cm_->Init()); - EXPECT_TRUE(cm_->SetLocalRenderer(&renderer)); - EXPECT_EQ(&renderer, fme_->local_renderer()); -} - // Test that logging options set before Init are applied properly, // and retained even after Init. TEST_F(ChannelManagerTest, SetLoggingBeforeInit) { diff --git a/session/media/mediamessages.cc b/session/media/mediamessages.cc index 1a5094e..6c9f681 100644 --- a/session/media/mediamessages.cc +++ b/session/media/mediamessages.cc @@ -34,7 +34,7 @@ #include "talk/p2p/base/constants.h" #include "talk/p2p/base/parsing.h" #include "talk/session/media/mediasessionclient.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" diff --git a/session/media/mediamessages_unittest.cc b/session/media/mediamessages_unittest.cc index c95053d..9ebd38c 100644 --- a/session/media/mediamessages_unittest.cc +++ b/session/media/mediamessages_unittest.cc @@ -32,7 +32,7 @@ #include "talk/p2p/base/constants.h" #include "talk/session/media/mediasessionclient.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/gunit.h" #include "webrtc/base/scoped_ptr.h" diff --git a/session/media/mediamonitor.h b/session/media/mediamonitor.h index d549362..89740a8 100644 --- a/session/media/mediamonitor.h +++ b/session/media/mediamonitor.h @@ -34,6 +34,7 @@ #include "webrtc/base/criticalsection.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/thread.h" +#include "webrtc/base/thread_annotations.h" namespace cricket { @@ -77,7 +78,7 @@ class MediaMonitorT : public MediaMonitor { media_info_.Clear(); media_channel_->GetStats(&media_info_); } - virtual void Update() { + virtual void Update() EXCLUSIVE_LOCKS_REQUIRED(crit_) { MI stats(media_info_); crit_.Leave(); SignalUpdate(media_channel_, stats); diff --git a/session/media/mediasession.cc b/session/media/mediasession.cc index 45e321f..92dd257 100644 --- a/session/media/mediasession.cc +++ b/session/media/mediasession.cc @@ -1170,28 +1170,28 @@ SessionDescription* MediaSessionDescriptionFactory::CreateOffer( if (current_description) { ContentInfos::const_iterator it = current_description->contents().begin(); for (; it != current_description->contents().end(); ++it) { - if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO) && options.has_audio) { + if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { if (!AddAudioContentForOffer(options, current_description, audio_rtp_extensions, audio_codecs, ¤t_streams, offer.get())) { return NULL; } audio_added = true; - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO) && - options.has_video) { + } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { if (!AddVideoContentForOffer(options, current_description, video_rtp_extensions, video_codecs, ¤t_streams, offer.get())) { return NULL; } video_added = true; - } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA) && - options.has_data()) { + } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) { if (!AddDataContentForOffer(options, current_description, &data_codecs, ¤t_streams, offer.get())) { return NULL; } data_added = true; + } else { + ASSERT(false); } } } @@ -1459,6 +1459,7 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer( bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); SetMediaProtocol(secure_transport, audio.get()); + desc->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release()); if (!AddTransportOffer(CN_AUDIO, options.transport_options, current_description, desc)) { diff --git a/session/media/mediasessionclient.cc b/session/media/mediasessionclient.cc index a1096b6..826909a 100644 --- a/session/media/mediasessionclient.cc +++ b/session/media/mediasessionclient.cc @@ -36,8 +36,8 @@ #include "talk/p2p/base/parsing.h" #include "talk/session/media/mediamessages.h" #include "talk/session/media/srtpfilter.h" -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" #include "talk/xmpp/constants.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" diff --git a/session/media/mediasessionclient_unittest.cc b/session/media/mediasessionclient_unittest.cc index 3e36b5a..2998228 100644 --- a/session/media/mediasessionclient_unittest.cc +++ b/session/media/mediasessionclient_unittest.cc @@ -34,9 +34,9 @@ #include "talk/p2p/base/constants.h" #include "talk/p2p/client/basicportallocator.h" #include "talk/session/media/mediasessionclient.h" -#include "talk/xmllite/xmlbuilder.h" -#include "talk/xmllite/xmlelement.h" -#include "talk/xmllite/xmlprinter.h" +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" #include "talk/xmpp/constants.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" diff --git a/session/tunnel/securetunnelsessionclient.cc b/session/tunnel/securetunnelsessionclient.cc index f4a3346..cb41c3b 100644 --- a/session/tunnel/securetunnelsessionclient.cc +++ b/session/tunnel/securetunnelsessionclient.cc @@ -30,7 +30,7 @@ #include "talk/p2p/base/transportchannel.h" #include "talk/session/tunnel/pseudotcpchannel.h" #include "talk/session/tunnel/securetunnelsessionclient.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "webrtc/base/basicdefs.h" #include "webrtc/base/basictypes.h" #include "webrtc/base/common.h" diff --git a/session/tunnel/tunnelsessionclient.cc b/session/tunnel/tunnelsessionclient.cc index 12275fb..7221db4 100644 --- a/session/tunnel/tunnelsessionclient.cc +++ b/session/tunnel/tunnelsessionclient.cc @@ -28,7 +28,7 @@ #include "pseudotcpchannel.h" #include "talk/p2p/base/constants.h" #include "talk/p2p/base/transportchannel.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "tunnelsessionclient.h" #include "webrtc/base/basicdefs.h" #include "webrtc/base/basictypes.h" diff --git a/session/tunnel/tunnelsessionclient.h b/session/tunnel/tunnelsessionclient.h index ac12faf..d3fa64a 100644 --- a/session/tunnel/tunnelsessionclient.h +++ b/session/tunnel/tunnelsessionclient.h @@ -36,7 +36,7 @@ #include "talk/p2p/base/sessionclient.h" #include "talk/p2p/base/sessiondescription.h" #include "talk/p2p/base/sessionmanager.h" -#include "talk/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/qname.h" #include "talk/xmpp/constants.h" #include "webrtc/base/criticalsection.h" #include "webrtc/base/stream.h" diff --git a/sound/alsasoundsystem.cc b/sound/alsasoundsystem.cc deleted file mode 100644 index d9960bb..0000000 --- a/sound/alsasoundsystem.cc +++ /dev/null @@ -1,761 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/alsasoundsystem.h" - -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundinputstreaminterface.h" -#include "talk/sound/soundoutputstreaminterface.h" -#include "webrtc/base/common.h" -#include "webrtc/base/logging.h" -#include "webrtc/base/scoped_ptr.h" -#include "webrtc/base/stringutils.h" -#include "webrtc/base/timeutils.h" -#include "webrtc/base/worker.h" - -namespace cricket { - -// Lookup table from the cricket format enum in soundsysteminterface.h to -// ALSA's enums. -static const snd_pcm_format_t kCricketFormatToAlsaFormatTable[] = { - // The order here must match the order in soundsysteminterface.h - SND_PCM_FORMAT_S16_LE, -}; - -// Lookup table for the size of a single sample of a given format. -static const size_t kCricketFormatToSampleSizeTable[] = { - // The order here must match the order in soundsysteminterface.h - sizeof(int16_t), // 2 -}; - -// Minimum latency we allow, in microseconds. This is more or less arbitrary, -// but it has to be at least large enough to be able to buffer data during a -// missed context switch, and the typical Linux scheduling quantum is 10ms. -static const int kMinimumLatencyUsecs = 20 * 1000; - -// The latency we'll use for kNoLatencyRequirements (chosen arbitrarily). -static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2; - -// We translate newlines in ALSA device descriptions to hyphens. -static const char kAlsaDescriptionSearch[] = "\n"; -static const char kAlsaDescriptionReplace[] = " - "; - -class AlsaDeviceLocator : public SoundDeviceLocator { - public: - AlsaDeviceLocator(const std::string &name, - const std::string &device_name) - : SoundDeviceLocator(name, device_name) { - // The ALSA descriptions have newlines in them, which won't show up in - // a drop-down box. Replace them with hyphens. - rtc::replace_substrs(kAlsaDescriptionSearch, - sizeof(kAlsaDescriptionSearch) - 1, - kAlsaDescriptionReplace, - sizeof(kAlsaDescriptionReplace) - 1, - &name_); - } - - virtual SoundDeviceLocator *Copy() const { - return new AlsaDeviceLocator(*this); - } -}; - -// Functionality that is common to both AlsaInputStream and AlsaOutputStream. -class AlsaStream { - public: - AlsaStream(AlsaSoundSystem *alsa, - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq) - : alsa_(alsa), - handle_(handle), - frame_size_(frame_size), - wait_timeout_ms_(wait_timeout_ms), - flags_(flags), - freq_(freq) { - } - - ~AlsaStream() { - Close(); - } - - // Waits for the stream to be ready to accept/return more data, and returns - // how much can be written/read, or 0 if we need to Wait() again. - snd_pcm_uframes_t Wait() { - snd_pcm_sframes_t frames; - // Ideally we would not use snd_pcm_wait() and instead hook snd_pcm_poll_* - // into PhysicalSocketServer, but PhysicalSocketServer is nasty enough - // already and the current clients of SoundSystemInterface do not run - // anything else on their worker threads, so snd_pcm_wait() is good enough. - frames = symbol_table()->snd_pcm_avail_update()(handle_); - if (frames < 0) { - LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); - Recover(frames); - return 0; - } else if (frames > 0) { - // Already ready, so no need to wait. - return frames; - } - // Else no space/data available, so must wait. - int ready = symbol_table()->snd_pcm_wait()(handle_, wait_timeout_ms_); - if (ready < 0) { - LOG(LS_ERROR) << "snd_pcm_wait(): " << GetError(ready); - Recover(ready); - return 0; - } else if (ready == 0) { - // Timeout, so nothing can be written/read right now. - // We set the timeout to twice the requested latency, so continuous - // timeouts are indicative of a problem, so log as a warning. - LOG(LS_WARNING) << "Timeout while waiting on stream"; - return 0; - } - // Else ready > 0 (i.e., 1), so it's ready. Get count. - frames = symbol_table()->snd_pcm_avail_update()(handle_); - if (frames < 0) { - LOG(LS_ERROR) << "snd_pcm_avail_update(): " << GetError(frames); - Recover(frames); - return 0; - } else if (frames == 0) { - // wait() said we were ready, so this ought to have been positive. Has - // been observed to happen in practice though. - LOG(LS_WARNING) << "Spurious wake-up"; - } - return frames; - } - - int CurrentDelayUsecs() { - if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) { - return 0; - } - - snd_pcm_sframes_t delay; - int err = symbol_table()->snd_pcm_delay()(handle_, &delay); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_delay(): " << GetError(err); - Recover(err); - // We'd rather continue playout/capture with an incorrect delay than stop - // it altogether, so return a valid value. - return 0; - } - // The delay is in frames. Convert to microseconds. - return delay * rtc::kNumMicrosecsPerSec / freq_; - } - - // Used to recover from certain recoverable errors, principally buffer overrun - // or underrun (identified as EPIPE). Without calling this the stream stays - // in the error state forever. - bool Recover(int error) { - int err; - err = symbol_table()->snd_pcm_recover()( - handle_, - error, - // Silent; i.e., no logging on stderr. - 1); - if (err != 0) { - // Docs say snd_pcm_recover returns the original error if it is not one - // of the recoverable ones, so this log message will probably contain the - // same error twice. - LOG(LS_ERROR) << "Unable to recover from \"" << GetError(error) << "\": " - << GetError(err); - return false; - } - if (error == -EPIPE && // Buffer underrun/overrun. - symbol_table()->snd_pcm_stream()(handle_) == SND_PCM_STREAM_CAPTURE) { - // For capture streams we also have to repeat the explicit start() to get - // data flowing again. - err = symbol_table()->snd_pcm_start()(handle_); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); - return false; - } - } - return true; - } - - bool Close() { - if (handle_) { - int err; - err = symbol_table()->snd_pcm_drop()(handle_); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_drop(): " << GetError(err); - // Continue anyways. - } - err = symbol_table()->snd_pcm_close()(handle_); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); - // Continue anyways. - } - handle_ = NULL; - } - return true; - } - - AlsaSymbolTable *symbol_table() { - return &alsa_->symbol_table_; - } - - snd_pcm_t *handle() { - return handle_; - } - - const char *GetError(int err) { - return alsa_->GetError(err); - } - - size_t frame_size() { - return frame_size_; - } - - private: - AlsaSoundSystem *alsa_; - snd_pcm_t *handle_; - size_t frame_size_; - int wait_timeout_ms_; - int flags_; - int freq_; - - DISALLOW_COPY_AND_ASSIGN(AlsaStream); -}; - -// Implementation of an input stream. See soundinputstreaminterface.h regarding -// thread-safety. -class AlsaInputStream : - public SoundInputStreamInterface, - private rtc::Worker { - public: - AlsaInputStream(AlsaSoundSystem *alsa, - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq) - : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq), - buffer_size_(0) { - } - - virtual ~AlsaInputStream() { - bool success = StopReading(); - // We need that to live. - VERIFY(success); - } - - virtual bool StartReading() { - return StartWork(); - } - - virtual bool StopReading() { - return StopWork(); - } - - virtual bool GetVolume(int *volume) { - // TODO: Implement this. - return false; - } - - virtual bool SetVolume(int volume) { - // TODO: Implement this. - return false; - } - - virtual bool Close() { - return StopReading() && stream_.Close(); - } - - virtual int LatencyUsecs() { - return stream_.CurrentDelayUsecs(); - } - - private: - // Inherited from Worker. - virtual void OnStart() { - HaveWork(); - } - - // Inherited from Worker. - virtual void OnHaveWork() { - // Block waiting for data. - snd_pcm_uframes_t avail = stream_.Wait(); - if (avail > 0) { - // Data is available. - size_t size = avail * stream_.frame_size(); - if (size > buffer_size_) { - // Must increase buffer size. - buffer_.reset(new char[size]); - buffer_size_ = size; - } - // Read all the data. - snd_pcm_sframes_t read = stream_.symbol_table()->snd_pcm_readi()( - stream_.handle(), - buffer_.get(), - avail); - if (read < 0) { - LOG(LS_ERROR) << "snd_pcm_readi(): " << GetError(read); - stream_.Recover(read); - } else if (read == 0) { - // Docs say this shouldn't happen. - ASSERT(false); - LOG(LS_ERROR) << "No data?"; - } else { - // Got data. Pass it off to the app. - SignalSamplesRead(buffer_.get(), - read * stream_.frame_size(), - this); - } - } - // Check for more data with no delay, after any pending messages are - // dispatched. - HaveWork(); - } - - // Inherited from Worker. - virtual void OnStop() { - // Nothing to do. - } - - const char *GetError(int err) { - return stream_.GetError(err); - } - - AlsaStream stream_; - rtc::scoped_ptr<char[]> buffer_; - size_t buffer_size_; - - DISALLOW_COPY_AND_ASSIGN(AlsaInputStream); -}; - -// Implementation of an output stream. See soundoutputstreaminterface.h -// regarding thread-safety. -class AlsaOutputStream : - public SoundOutputStreamInterface, - private rtc::Worker { - public: - AlsaOutputStream(AlsaSoundSystem *alsa, - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq) - : stream_(alsa, handle, frame_size, wait_timeout_ms, flags, freq) { - } - - virtual ~AlsaOutputStream() { - bool success = DisableBufferMonitoring(); - // We need that to live. - VERIFY(success); - } - - virtual bool EnableBufferMonitoring() { - return StartWork(); - } - - virtual bool DisableBufferMonitoring() { - return StopWork(); - } - - virtual bool WriteSamples(const void *sample_data, - size_t size) { - if (size % stream_.frame_size() != 0) { - // No client of SoundSystemInterface does this, so let's not support it. - // (If we wanted to support it, we'd basically just buffer the fractional - // frame until we get more data.) - ASSERT(false); - LOG(LS_ERROR) << "Writes with fractional frames are not supported"; - return false; - } - snd_pcm_uframes_t frames = size / stream_.frame_size(); - snd_pcm_sframes_t written = stream_.symbol_table()->snd_pcm_writei()( - stream_.handle(), - sample_data, - frames); - if (written < 0) { - LOG(LS_ERROR) << "snd_pcm_writei(): " << GetError(written); - stream_.Recover(written); - return false; - } else if (static_cast<snd_pcm_uframes_t>(written) < frames) { - // Shouldn't happen. Drop the rest of the data. - LOG(LS_ERROR) << "Stream wrote only " << written << " of " << frames - << " frames!"; - return false; - } - return true; - } - - virtual bool GetVolume(int *volume) { - // TODO: Implement this. - return false; - } - - virtual bool SetVolume(int volume) { - // TODO: Implement this. - return false; - } - - virtual bool Close() { - return DisableBufferMonitoring() && stream_.Close(); - } - - virtual int LatencyUsecs() { - return stream_.CurrentDelayUsecs(); - } - - private: - // Inherited from Worker. - virtual void OnStart() { - HaveWork(); - } - - // Inherited from Worker. - virtual void OnHaveWork() { - snd_pcm_uframes_t avail = stream_.Wait(); - if (avail > 0) { - size_t space = avail * stream_.frame_size(); - SignalBufferSpace(space, this); - } - HaveWork(); - } - - // Inherited from Worker. - virtual void OnStop() { - // Nothing to do. - } - - const char *GetError(int err) { - return stream_.GetError(err); - } - - AlsaStream stream_; - - DISALLOW_COPY_AND_ASSIGN(AlsaOutputStream); -}; - -AlsaSoundSystem::AlsaSoundSystem() : initialized_(false) {} - -AlsaSoundSystem::~AlsaSoundSystem() { - // Not really necessary, because Terminate() doesn't really do anything. - Terminate(); -} - -bool AlsaSoundSystem::Init() { - if (IsInitialized()) { - return true; - } - - // Load libasound. - if (!symbol_table_.Load()) { - // Very odd for a Linux machine to not have a working libasound ... - LOG(LS_ERROR) << "Failed to load symbol table"; - return false; - } - - initialized_ = true; - - return true; -} - -void AlsaSoundSystem::Terminate() { - if (!IsInitialized()) { - return; - } - - initialized_ = false; - - // We do not unload the symbol table because we may need it again soon if - // Init() is called again. -} - -bool AlsaSoundSystem::EnumeratePlaybackDevices( - SoundDeviceLocatorList *devices) { - return EnumerateDevices(devices, false); -} - -bool AlsaSoundSystem::EnumerateCaptureDevices( - SoundDeviceLocatorList *devices) { - return EnumerateDevices(devices, true); -} - -bool AlsaSoundSystem::GetDefaultPlaybackDevice(SoundDeviceLocator **device) { - return GetDefaultDevice(device); -} - -bool AlsaSoundSystem::GetDefaultCaptureDevice(SoundDeviceLocator **device) { - return GetDefaultDevice(device); -} - -SoundOutputStreamInterface *AlsaSoundSystem::OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return OpenDevice<SoundOutputStreamInterface>( - device, - params, - SND_PCM_STREAM_PLAYBACK, - &AlsaSoundSystem::StartOutputStream); -} - -SoundInputStreamInterface *AlsaSoundSystem::OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return OpenDevice<SoundInputStreamInterface>( - device, - params, - SND_PCM_STREAM_CAPTURE, - &AlsaSoundSystem::StartInputStream); -} - -const char *AlsaSoundSystem::GetName() const { - return "ALSA"; -} - -bool AlsaSoundSystem::EnumerateDevices( - SoundDeviceLocatorList *devices, - bool capture_not_playback) { - ClearSoundDeviceLocatorList(devices); - - if (!IsInitialized()) { - return false; - } - - const char *type = capture_not_playback ? "Input" : "Output"; - // dmix and dsnoop are only for playback and capture, respectively, but ALSA - // stupidly includes them in both lists. - const char *ignore_prefix = capture_not_playback ? "dmix:" : "dsnoop:"; - // (ALSA lists many more "devices" of questionable interest, but we show them - // just in case the weird devices may actually be desirable for some - // users/systems.) - const char *ignore_default = "default"; - const char *ignore_null = "null"; - const char *ignore_pulse = "pulse"; - // The 'pulse' entry has a habit of mysteriously disappearing when you query - // a second time. Remove it from our list. (GIPS lib did the same thing.) - int err; - - void **hints; - err = symbol_table_.snd_device_name_hint()(-1, // All cards - "pcm", // Only PCM devices - &hints); - if (err != 0) { - LOG(LS_ERROR) << "snd_device_name_hint(): " << GetError(err); - return false; - } - - for (void **list = hints; *list != NULL; ++list) { - char *actual_type = symbol_table_.snd_device_name_get_hint()(*list, "IOID"); - if (actual_type) { // NULL means it's both. - bool wrong_type = (strcmp(actual_type, type) != 0); - free(actual_type); - if (wrong_type) { - // Wrong type of device (i.e., input vs. output). - continue; - } - } - - char *name = symbol_table_.snd_device_name_get_hint()(*list, "NAME"); - if (!name) { - LOG(LS_ERROR) << "Device has no name???"; - // Skip it. - continue; - } - - // Now check if we actually want to show this device. - if (strcmp(name, ignore_default) != 0 && - strcmp(name, ignore_null) != 0 && - strcmp(name, ignore_pulse) != 0 && - !rtc::starts_with(name, ignore_prefix)) { - - // Yes, we do. - char *desc = symbol_table_.snd_device_name_get_hint()(*list, "DESC"); - if (!desc) { - // Virtual devices don't necessarily have descriptions. Use their names - // instead (not pretty!). - desc = name; - } - - AlsaDeviceLocator *device = new AlsaDeviceLocator(desc, name); - - devices->push_back(device); - - if (desc != name) { - free(desc); - } - } - - free(name); - } - - err = symbol_table_.snd_device_name_free_hint()(hints); - if (err != 0) { - LOG(LS_ERROR) << "snd_device_name_free_hint(): " << GetError(err); - // Continue and return true anyways, since we did get the whole list. - } - - return true; -} - -bool AlsaSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) { - if (!IsInitialized()) { - return false; - } - *device = new AlsaDeviceLocator("Default device", "default"); - return true; -} - -inline size_t AlsaSoundSystem::FrameSize(const OpenParams ¶ms) { - ASSERT(static_cast<int>(params.format) < - ARRAY_SIZE(kCricketFormatToSampleSizeTable)); - return kCricketFormatToSampleSizeTable[params.format] * params.channels; -} - -template <typename StreamInterface> -StreamInterface *AlsaSoundSystem::OpenDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms, - snd_pcm_stream_t type, - StreamInterface *(AlsaSoundSystem::*start_fn)( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq)) { - - if (!IsInitialized()) { - return NULL; - } - - StreamInterface *stream; - int err; - - const char *dev = static_cast<const AlsaDeviceLocator *>(device)-> - device_name().c_str(); - - snd_pcm_t *handle = NULL; - err = symbol_table_.snd_pcm_open()( - &handle, - dev, - type, - // No flags. - 0); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_open(" << dev << "): " << GetError(err); - return NULL; - } - LOG(LS_VERBOSE) << "Opening " << dev; - ASSERT(handle); // If open succeeded, handle ought to be valid - - // Compute requested latency in microseconds. - int latency; - if (params.latency == kNoLatencyRequirements) { - latency = kDefaultLatencyUsecs; - } else { - // kLowLatency is 0, so we treat it the same as a request for zero latency. - // Compute what the user asked for. - latency = rtc::kNumMicrosecsPerSec * - params.latency / - params.freq / - FrameSize(params); - // And this is what we'll actually use. - latency = rtc::_max(latency, kMinimumLatencyUsecs); - } - - ASSERT(static_cast<int>(params.format) < - ARRAY_SIZE(kCricketFormatToAlsaFormatTable)); - - err = symbol_table_.snd_pcm_set_params()( - handle, - kCricketFormatToAlsaFormatTable[params.format], - // SoundSystemInterface only supports interleaved audio. - SND_PCM_ACCESS_RW_INTERLEAVED, - params.channels, - params.freq, - 1, // Allow ALSA to resample. - latency); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_set_params(): " << GetError(err); - goto fail; - } - - err = symbol_table_.snd_pcm_prepare()(handle); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_prepare(): " << GetError(err); - goto fail; - } - - stream = (this->*start_fn)( - handle, - FrameSize(params), - // We set the wait time to twice the requested latency, so that wait - // timeouts should be rare. - 2 * latency / rtc::kNumMicrosecsPerMillisec, - params.flags, - params.freq); - if (stream) { - return stream; - } - // Else fall through. - - fail: - err = symbol_table_.snd_pcm_close()(handle); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_close(): " << GetError(err); - } - return NULL; -} - -SoundOutputStreamInterface *AlsaSoundSystem::StartOutputStream( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq) { - // Nothing to do here but instantiate the stream. - return new AlsaOutputStream( - this, handle, frame_size, wait_timeout_ms, flags, freq); -} - -SoundInputStreamInterface *AlsaSoundSystem::StartInputStream( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq) { - // Output streams start automatically once enough data has been written, but - // input streams must be started manually or else snd_pcm_wait() will never - // return true. - int err; - err = symbol_table_.snd_pcm_start()(handle); - if (err != 0) { - LOG(LS_ERROR) << "snd_pcm_start(): " << GetError(err); - return NULL; - } - return new AlsaInputStream( - this, handle, frame_size, wait_timeout_ms, flags, freq); -} - -inline const char *AlsaSoundSystem::GetError(int err) { - return symbol_table_.snd_strerror()(err); -} - -} // namespace cricket diff --git a/sound/alsasoundsystem.h b/sound/alsasoundsystem.h deleted file mode 100644 index b3abfb0..0000000 --- a/sound/alsasoundsystem.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_ALSASOUNDSYSTEM_H_ -#define TALK_SOUND_ALSASOUNDSYSTEM_H_ - -#include "talk/sound/alsasymboltable.h" -#include "talk/sound/soundsysteminterface.h" -#include "webrtc/base/constructormagic.h" - -namespace cricket { - -class AlsaStream; -class AlsaInputStream; -class AlsaOutputStream; - -// Sound system implementation for ALSA, the predominant sound device API on -// Linux (but typically not used directly by applications anymore). -class AlsaSoundSystem : public SoundSystemInterface { - friend class AlsaStream; - friend class AlsaInputStream; - friend class AlsaOutputStream; - public: - static SoundSystemInterface *Create() { - return new AlsaSoundSystem(); - } - - AlsaSoundSystem(); - - virtual ~AlsaSoundSystem(); - - virtual bool Init(); - virtual void Terminate(); - - virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices); - virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices); - - virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device); - virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device); - - virtual SoundOutputStreamInterface *OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - virtual SoundInputStreamInterface *OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - - virtual const char *GetName() const; - - private: - bool IsInitialized() { return initialized_; } - - bool EnumerateDevices(SoundDeviceLocatorList *devices, - bool capture_not_playback); - - bool GetDefaultDevice(SoundDeviceLocator **device); - - static size_t FrameSize(const OpenParams ¶ms); - - template <typename StreamInterface> - StreamInterface *OpenDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms, - snd_pcm_stream_t type, - StreamInterface *(AlsaSoundSystem::*start_fn)( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq)); - - SoundOutputStreamInterface *StartOutputStream( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq); - - SoundInputStreamInterface *StartInputStream( - snd_pcm_t *handle, - size_t frame_size, - int wait_timeout_ms, - int flags, - int freq); - - const char *GetError(int err); - - bool initialized_; - AlsaSymbolTable symbol_table_; - - DISALLOW_COPY_AND_ASSIGN(AlsaSoundSystem); -}; - -} // namespace cricket - -#endif // TALK_SOUND_ALSASOUNDSYSTEM_H_ diff --git a/sound/alsasymboltable.cc b/sound/alsasymboltable.cc deleted file mode 100644 index 570b4b4..0000000 --- a/sound/alsasymboltable.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/alsasymboltable.h" - -namespace cricket { - -#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME ALSA_SYMBOLS_CLASS_NAME -#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST ALSA_SYMBOLS_LIST -#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libasound.so.2" -#include "webrtc/base/latebindingsymboltable.cc.def" - -} // namespace cricket diff --git a/sound/alsasymboltable.h b/sound/alsasymboltable.h deleted file mode 100644 index 98f1645..0000000 --- a/sound/alsasymboltable.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_ALSASYMBOLTABLE_H_ -#define TALK_SOUND_ALSASYMBOLTABLE_H_ - -#include <alsa/asoundlib.h> - -#include "webrtc/base/latebindingsymboltable.h" - -namespace cricket { - -#define ALSA_SYMBOLS_CLASS_NAME AlsaSymbolTable -// The ALSA symbols we need, as an X-Macro list. -// This list must contain precisely every libasound function that is used in -// alsasoundsystem.cc. -#define ALSA_SYMBOLS_LIST \ - X(snd_device_name_free_hint) \ - X(snd_device_name_get_hint) \ - X(snd_device_name_hint) \ - X(snd_pcm_avail_update) \ - X(snd_pcm_close) \ - X(snd_pcm_delay) \ - X(snd_pcm_drop) \ - X(snd_pcm_open) \ - X(snd_pcm_prepare) \ - X(snd_pcm_readi) \ - X(snd_pcm_recover) \ - X(snd_pcm_set_params) \ - X(snd_pcm_start) \ - X(snd_pcm_stream) \ - X(snd_pcm_wait) \ - X(snd_pcm_writei) \ - X(snd_strerror) - -#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME ALSA_SYMBOLS_CLASS_NAME -#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST ALSA_SYMBOLS_LIST -#include "webrtc/base/latebindingsymboltable.h.def" - -} // namespace cricket - -#endif // TALK_SOUND_ALSASYMBOLTABLE_H_ diff --git a/sound/automaticallychosensoundsystem.h b/sound/automaticallychosensoundsystem.h deleted file mode 100644 index 10ea0df..0000000 --- a/sound/automaticallychosensoundsystem.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_ -#define TALK_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_ - -#include "talk/sound/soundsysteminterface.h" -#include "talk/sound/soundsystemproxy.h" -#include "webrtc/base/common.h" -#include "webrtc/base/logging.h" -#include "webrtc/base/scoped_ptr.h" - -namespace cricket { - -// A function type that creates an instance of a sound system implementation. -typedef SoundSystemInterface *(*SoundSystemCreator)(); - -// An AutomaticallyChosenSoundSystem is a sound system proxy that defers to -// an instance of the first sound system implementation in a list that -// successfully initializes. -template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems> -class AutomaticallyChosenSoundSystem : public SoundSystemProxy { - public: - // Chooses and initializes the underlying sound system. - virtual bool Init(); - // Terminates the underlying sound system implementation, but caches it for - // future re-use. - virtual void Terminate(); - - virtual const char *GetName() const; - - private: - rtc::scoped_ptr<SoundSystemInterface> sound_systems_[kNumSoundSystems]; -}; - -template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems> -bool AutomaticallyChosenSoundSystem<kSoundSystemCreators, - kNumSoundSystems>::Init() { - if (wrapped_) { - return true; - } - for (int i = 0; i < kNumSoundSystems; ++i) { - if (!sound_systems_[i].get()) { - sound_systems_[i].reset((*kSoundSystemCreators[i])()); - } - if (sound_systems_[i]->Init()) { - // This is the first sound system in the list to successfully - // initialize, so we're done. - wrapped_ = sound_systems_[i].get(); - break; - } - // Else it failed to initialize, so try the remaining ones. - } - if (!wrapped_) { - LOG(LS_ERROR) << "Failed to find a usable sound system"; - return false; - } - LOG(LS_INFO) << "Selected " << wrapped_->GetName() << " sound system"; - return true; -} - -template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems> -void AutomaticallyChosenSoundSystem<kSoundSystemCreators, - kNumSoundSystems>::Terminate() { - if (!wrapped_) { - return; - } - wrapped_->Terminate(); - wrapped_ = NULL; - // We do not free the scoped_ptrs because we may be re-init'ed soon. -} - -template <const SoundSystemCreator kSoundSystemCreators[], int kNumSoundSystems> -const char *AutomaticallyChosenSoundSystem<kSoundSystemCreators, - kNumSoundSystems>::GetName() const { - return wrapped_ ? wrapped_->GetName() : "automatic"; -} - -} // namespace cricket - -#endif // TALK_SOUND_AUTOMATICALLYCHOSENSOUNDSYSTEM_H_ diff --git a/sound/automaticallychosensoundsystem_unittest.cc b/sound/automaticallychosensoundsystem_unittest.cc deleted file mode 100644 index 813828d..0000000 --- a/sound/automaticallychosensoundsystem_unittest.cc +++ /dev/null @@ -1,214 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/automaticallychosensoundsystem.h" -#include "talk/sound/nullsoundsystem.h" -#include "webrtc/base/gunit.h" - -namespace cricket { - -class NeverFailsToFailSoundSystem : public NullSoundSystem { - public: - // Overrides superclass. - virtual bool Init() { - return false; - } - - static SoundSystemInterface *Create() { - return new NeverFailsToFailSoundSystem(); - } -}; - -class InitCheckingSoundSystem1 : public NullSoundSystem { - public: - // Overrides superclass. - virtual bool Init() { - created_ = true; - return true; - } - - static SoundSystemInterface *Create() { - return new InitCheckingSoundSystem1(); - } - - static bool created_; -}; - -bool InitCheckingSoundSystem1::created_ = false; - -class InitCheckingSoundSystem2 : public NullSoundSystem { - public: - // Overrides superclass. - virtual bool Init() { - created_ = true; - return true; - } - - static SoundSystemInterface *Create() { - return new InitCheckingSoundSystem2(); - } - - static bool created_; -}; - -bool InitCheckingSoundSystem2::created_ = false; - -class DeletionCheckingSoundSystem1 : public NeverFailsToFailSoundSystem { - public: - virtual ~DeletionCheckingSoundSystem1() { - deleted_ = true; - } - - static SoundSystemInterface *Create() { - return new DeletionCheckingSoundSystem1(); - } - - static bool deleted_; -}; - -bool DeletionCheckingSoundSystem1::deleted_ = false; - -class DeletionCheckingSoundSystem2 : public NeverFailsToFailSoundSystem { - public: - virtual ~DeletionCheckingSoundSystem2() { - deleted_ = true; - } - - static SoundSystemInterface *Create() { - return new DeletionCheckingSoundSystem2(); - } - - static bool deleted_; -}; - -bool DeletionCheckingSoundSystem2::deleted_ = false; - -class DeletionCheckingSoundSystem3 : public NullSoundSystem { - public: - virtual ~DeletionCheckingSoundSystem3() { - deleted_ = true; - } - - static SoundSystemInterface *Create() { - return new DeletionCheckingSoundSystem3(); - } - - static bool deleted_; -}; - -bool DeletionCheckingSoundSystem3::deleted_ = false; - -extern const SoundSystemCreator kSingleSystemFailingCreators[] = { - &NeverFailsToFailSoundSystem::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, SingleSystemFailing) { - AutomaticallyChosenSoundSystem< - kSingleSystemFailingCreators, - ARRAY_SIZE(kSingleSystemFailingCreators)> sound_system; - EXPECT_FALSE(sound_system.Init()); -} - -extern const SoundSystemCreator kSingleSystemSucceedingCreators[] = { - &NullSoundSystem::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, SingleSystemSucceeding) { - AutomaticallyChosenSoundSystem< - kSingleSystemSucceedingCreators, - ARRAY_SIZE(kSingleSystemSucceedingCreators)> sound_system; - EXPECT_TRUE(sound_system.Init()); -} - -extern const SoundSystemCreator - kFailedFirstSystemResultsInUsingSecondCreators[] = { - &NeverFailsToFailSoundSystem::Create, - &NullSoundSystem::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, FailedFirstSystemResultsInUsingSecond) { - AutomaticallyChosenSoundSystem< - kFailedFirstSystemResultsInUsingSecondCreators, - ARRAY_SIZE(kFailedFirstSystemResultsInUsingSecondCreators)> sound_system; - EXPECT_TRUE(sound_system.Init()); -} - -extern const SoundSystemCreator kEarlierEntriesHavePriorityCreators[] = { - &InitCheckingSoundSystem1::Create, - &InitCheckingSoundSystem2::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, EarlierEntriesHavePriority) { - AutomaticallyChosenSoundSystem< - kEarlierEntriesHavePriorityCreators, - ARRAY_SIZE(kEarlierEntriesHavePriorityCreators)> sound_system; - InitCheckingSoundSystem1::created_ = false; - InitCheckingSoundSystem2::created_ = false; - EXPECT_TRUE(sound_system.Init()); - EXPECT_TRUE(InitCheckingSoundSystem1::created_); - EXPECT_FALSE(InitCheckingSoundSystem2::created_); -} - -extern const SoundSystemCreator kManySoundSystemsCreators[] = { - &NullSoundSystem::Create, - &NullSoundSystem::Create, - &NullSoundSystem::Create, - &NullSoundSystem::Create, - &NullSoundSystem::Create, - &NullSoundSystem::Create, - &NullSoundSystem::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, ManySoundSystems) { - AutomaticallyChosenSoundSystem< - kManySoundSystemsCreators, - ARRAY_SIZE(kManySoundSystemsCreators)> sound_system; - EXPECT_TRUE(sound_system.Init()); -} - -extern const SoundSystemCreator kDeletesAllCreatedSoundSystemsCreators[] = { - &DeletionCheckingSoundSystem1::Create, - &DeletionCheckingSoundSystem2::Create, - &DeletionCheckingSoundSystem3::Create, -}; - -TEST(AutomaticallyChosenSoundSystem, DeletesAllCreatedSoundSystems) { - typedef AutomaticallyChosenSoundSystem< - kDeletesAllCreatedSoundSystemsCreators, - ARRAY_SIZE(kDeletesAllCreatedSoundSystemsCreators)> TestSoundSystem; - TestSoundSystem *sound_system = new TestSoundSystem(); - DeletionCheckingSoundSystem1::deleted_ = false; - DeletionCheckingSoundSystem2::deleted_ = false; - DeletionCheckingSoundSystem3::deleted_ = false; - EXPECT_TRUE(sound_system->Init()); - delete sound_system; - EXPECT_TRUE(DeletionCheckingSoundSystem1::deleted_); - EXPECT_TRUE(DeletionCheckingSoundSystem2::deleted_); - EXPECT_TRUE(DeletionCheckingSoundSystem3::deleted_); -} - -} // namespace cricket diff --git a/sound/linuxsoundsystem.cc b/sound/linuxsoundsystem.cc deleted file mode 100644 index 7980a15..0000000 --- a/sound/linuxsoundsystem.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/linuxsoundsystem.h" - -#include "talk/sound/alsasoundsystem.h" -#include "talk/sound/pulseaudiosoundsystem.h" - -namespace cricket { - -const SoundSystemCreator kLinuxSoundSystemCreators[] = { -#ifdef HAVE_LIBPULSE - &PulseAudioSoundSystem::Create, -#endif - &AlsaSoundSystem::Create, -}; - -} // namespace cricket diff --git a/sound/linuxsoundsystem.h b/sound/linuxsoundsystem.h deleted file mode 100644 index eb48b88..0000000 --- a/sound/linuxsoundsystem.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_LINUXSOUNDSYSTEM_H_ -#define TALK_SOUND_LINUXSOUNDSYSTEM_H_ - -#include "talk/sound/automaticallychosensoundsystem.h" - -namespace cricket { - -extern const SoundSystemCreator kLinuxSoundSystemCreators[ -#ifdef HAVE_LIBPULSE - 2 -#else - 1 -#endif - ]; - -// The vast majority of Linux systems use ALSA for the device-level sound API, -// but an increasing number are using PulseAudio for the application API and -// only using ALSA internally in PulseAudio itself. But like everything on -// Linux this is user-configurable, so we need to support both and choose the -// right one at run-time. -// PulseAudioSoundSystem is designed to only successfully initialize if -// PulseAudio is installed and running, and if it is running then direct device -// access using ALSA typically won't work, so if PulseAudioSoundSystem -// initializes then we choose that. Otherwise we choose ALSA. -typedef AutomaticallyChosenSoundSystem< - kLinuxSoundSystemCreators, - ARRAY_SIZE(kLinuxSoundSystemCreators)> LinuxSoundSystem; - -} // namespace cricket - -#endif // TALK_SOUND_LINUXSOUNDSYSTEM_H_ diff --git a/sound/nullsoundsystem.cc b/sound/nullsoundsystem.cc deleted file mode 100644 index 3408d4c..0000000 --- a/sound/nullsoundsystem.cc +++ /dev/null @@ -1,174 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/nullsoundsystem.h" - -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundinputstreaminterface.h" -#include "talk/sound/soundoutputstreaminterface.h" -#include "webrtc/base/logging.h" - -namespace rtc { - -class Thread; - -} - -namespace cricket { - -// Name used for the single device and the sound system itself. -static const char kNullName[] = "null"; - -class NullSoundDeviceLocator : public SoundDeviceLocator { - public: - NullSoundDeviceLocator() : SoundDeviceLocator(kNullName, kNullName) {} - - virtual SoundDeviceLocator *Copy() const { - return new NullSoundDeviceLocator(); - } -}; - -class NullSoundInputStream : public SoundInputStreamInterface { - public: - virtual bool StartReading() { - return true; - } - - virtual bool StopReading() { - return true; - } - - virtual bool GetVolume(int *volume) { - *volume = SoundSystemInterface::kMinVolume; - return true; - } - - virtual bool SetVolume(int volume) { - return false; - } - - virtual bool Close() { - return true; - } - - virtual int LatencyUsecs() { - return 0; - } -}; - -class NullSoundOutputStream : public SoundOutputStreamInterface { - public: - virtual bool EnableBufferMonitoring() { - return true; - } - - virtual bool DisableBufferMonitoring() { - return true; - } - - virtual bool WriteSamples(const void *sample_data, - size_t size) { - LOG(LS_VERBOSE) << "Got " << size << " bytes of playback samples"; - return true; - } - - virtual bool GetVolume(int *volume) { - *volume = SoundSystemInterface::kMinVolume; - return true; - } - - virtual bool SetVolume(int volume) { - return false; - } - - virtual bool Close() { - return true; - } - - virtual int LatencyUsecs() { - return 0; - } -}; - -NullSoundSystem::~NullSoundSystem() { -} - -bool NullSoundSystem::Init() { - return true; -} - -void NullSoundSystem::Terminate() { - // Nothing to do. -} - -bool NullSoundSystem::EnumeratePlaybackDevices( - SoundSystemInterface::SoundDeviceLocatorList *devices) { - ClearSoundDeviceLocatorList(devices); - SoundDeviceLocator *device; - GetDefaultPlaybackDevice(&device); - devices->push_back(device); - return true; -} - -bool NullSoundSystem::EnumerateCaptureDevices( - SoundSystemInterface::SoundDeviceLocatorList *devices) { - ClearSoundDeviceLocatorList(devices); - SoundDeviceLocator *device; - GetDefaultCaptureDevice(&device); - devices->push_back(device); - return true; -} - -bool NullSoundSystem::GetDefaultPlaybackDevice( - SoundDeviceLocator **device) { - *device = new NullSoundDeviceLocator(); - return true; -} - -bool NullSoundSystem::GetDefaultCaptureDevice( - SoundDeviceLocator **device) { - *device = new NullSoundDeviceLocator(); - return true; -} - -SoundOutputStreamInterface *NullSoundSystem::OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return new NullSoundOutputStream(); -} - -SoundInputStreamInterface *NullSoundSystem::OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return new NullSoundInputStream(); -} - -const char *NullSoundSystem::GetName() const { - return kNullName; -} - -} // namespace cricket diff --git a/sound/nullsoundsystem.h b/sound/nullsoundsystem.h deleted file mode 100644 index 3edb4f9..0000000 --- a/sound/nullsoundsystem.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_NULLSOUNDSYSTEM_H_ -#define TALK_SOUND_NULLSOUNDSYSTEM_H_ - -#include "talk/sound/soundsysteminterface.h" - -namespace cricket { - -class SoundDeviceLocator; -class SoundInputStreamInterface; -class SoundOutputStreamInterface; - -// A simple reference sound system that drops output samples and generates -// no input samples. -class NullSoundSystem : public SoundSystemInterface { - public: - static SoundSystemInterface *Create() { - return new NullSoundSystem(); - } - - virtual ~NullSoundSystem(); - - virtual bool Init(); - virtual void Terminate(); - - virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices); - virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices); - - virtual SoundOutputStreamInterface *OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - virtual SoundInputStreamInterface *OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - - virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device); - virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device); - - virtual const char *GetName() const; -}; - -} // namespace cricket - -#endif // TALK_SOUND_NULLSOUNDSYSTEM_H_ diff --git a/sound/nullsoundsystemfactory.cc b/sound/nullsoundsystemfactory.cc deleted file mode 100644 index 089d51f..0000000 --- a/sound/nullsoundsystemfactory.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/nullsoundsystemfactory.h" - -#include "talk/sound/nullsoundsystem.h" - -namespace cricket { - -NullSoundSystemFactory::NullSoundSystemFactory() { -} - -NullSoundSystemFactory::~NullSoundSystemFactory() { -} - -bool NullSoundSystemFactory::SetupInstance() { - instance_.reset(new NullSoundSystem()); - return true; -} - -void NullSoundSystemFactory::CleanupInstance() { - instance_.reset(); -} - -} // namespace cricket diff --git a/sound/nullsoundsystemfactory.h b/sound/nullsoundsystemfactory.h deleted file mode 100644 index 71ae980..0000000 --- a/sound/nullsoundsystemfactory.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_ -#define TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_ - -#include "talk/sound/soundsystemfactory.h" - -namespace cricket { - -// A SoundSystemFactory that always returns a NullSoundSystem. Intended for -// testing. -class NullSoundSystemFactory : public SoundSystemFactory { - public: - NullSoundSystemFactory(); - virtual ~NullSoundSystemFactory(); - - protected: - // Inherited from SoundSystemFactory. - virtual bool SetupInstance(); - virtual void CleanupInstance(); -}; - -} // namespace cricket - -#endif // TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_ diff --git a/sound/platformsoundsystem.cc b/sound/platformsoundsystem.cc deleted file mode 100644 index c39fc83..0000000 --- a/sound/platformsoundsystem.cc +++ /dev/null @@ -1,48 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/platformsoundsystem.h" - -#include "webrtc/base/common.h" -#ifdef LINUX -#include "talk/sound/linuxsoundsystem.h" -#else -#include "talk/sound/nullsoundsystem.h" -#endif - -namespace cricket { - -SoundSystemInterface *CreatePlatformSoundSystem() { -#ifdef LINUX - return new LinuxSoundSystem(); -#else - ASSERT(false && "Not implemented"); - return new NullSoundSystem(); -#endif -} - -} // namespace cricket diff --git a/sound/platformsoundsystem.h b/sound/platformsoundsystem.h deleted file mode 100644 index 1a8d214..0000000 --- a/sound/platformsoundsystem.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_PLATFORMSOUNDSYSTEM_H_ -#define TALK_SOUND_PLATFORMSOUNDSYSTEM_H_ - -namespace cricket { - -class SoundSystemInterface; - -// Creates the sound system implementation for this platform. -SoundSystemInterface *CreatePlatformSoundSystem(); - -} // namespace cricket - -#endif // TALK_SOUND_PLATFORMSOUNDSYSTEM_H_ diff --git a/sound/platformsoundsystemfactory.cc b/sound/platformsoundsystemfactory.cc deleted file mode 100644 index 6c69954..0000000 --- a/sound/platformsoundsystemfactory.cc +++ /dev/null @@ -1,57 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/platformsoundsystemfactory.h" - -#include "talk/sound/platformsoundsystem.h" -#include "talk/sound/soundsysteminterface.h" - -namespace cricket { - -PlatformSoundSystemFactory::PlatformSoundSystemFactory() { -} - -PlatformSoundSystemFactory::~PlatformSoundSystemFactory() { -} - -bool PlatformSoundSystemFactory::SetupInstance() { - if (!instance_.get()) { - instance_.reset(CreatePlatformSoundSystem()); - } - if (!instance_->Init()) { - LOG(LS_ERROR) << "Can't initialize platform's sound system"; - return false; - } - return true; -} - -void PlatformSoundSystemFactory::CleanupInstance() { - instance_->Terminate(); - // We do not delete the sound system because we might be re-initialized soon. -} - -} // namespace cricket diff --git a/sound/platformsoundsystemfactory.h b/sound/platformsoundsystemfactory.h deleted file mode 100644 index 63ca863..0000000 --- a/sound/platformsoundsystemfactory.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_ -#define TALK_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_ - -#include "talk/sound/soundsystemfactory.h" - -namespace cricket { - -// A SoundSystemFactory that returns the platform's native sound system -// implementation. -class PlatformSoundSystemFactory : public SoundSystemFactory { - public: - PlatformSoundSystemFactory(); - virtual ~PlatformSoundSystemFactory(); - - protected: - // Inherited from SoundSystemFactory. - virtual bool SetupInstance(); - virtual void CleanupInstance(); -}; - -} // namespace cricket - -#endif // TALK_SOUND_PLATFORMSOUNDSYSTEMFACTORY_H_ - - diff --git a/sound/pulseaudiosoundsystem.cc b/sound/pulseaudiosoundsystem.cc deleted file mode 100644 index 24eea5c..0000000 --- a/sound/pulseaudiosoundsystem.cc +++ /dev/null @@ -1,1559 +0,0 @@ -/* - * libjingle - * Copyright 2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/pulseaudiosoundsystem.h" - -#ifdef HAVE_LIBPULSE - -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundinputstreaminterface.h" -#include "talk/sound/soundoutputstreaminterface.h" -#include "webrtc/base/common.h" -#include "webrtc/base/fileutils.h" // for GetApplicationName() -#include "webrtc/base/logging.h" -#include "webrtc/base/timeutils.h" -#include "webrtc/base/worker.h" - -namespace cricket { - -// First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY. -static const uint32_t kAdjustLatencyProtocolVersion = 13; - -// Lookup table from the cricket format enum in soundsysteminterface.h to -// Pulse's enums. -static const pa_sample_format_t kCricketFormatToPulseFormatTable[] = { - // The order here must match the order in soundsysteminterface.h - PA_SAMPLE_S16LE, -}; - -// Some timing constants for optimal operation. See -// https://tango.0pointer.de/pipermail/pulseaudio-discuss/2008-January/001170.html -// for a good explanation of some of the factors that go into this. - -// Playback. - -// For playback, there is a round-trip delay to fill the server-side playback -// buffer, so setting too low of a latency is a buffer underflow risk. We will -// automatically increase the latency if a buffer underflow does occur, but we -// also enforce a sane minimum at start-up time. Anything lower would be -// virtually guaranteed to underflow at least once, so there's no point in -// allowing lower latencies. -static const int kPlaybackLatencyMinimumMsecs = 20; -// Every time a playback stream underflows, we will reconfigure it with target -// latency that is greater by this amount. -static const int kPlaybackLatencyIncrementMsecs = 20; -// We also need to configure a suitable request size. Too small and we'd burn -// CPU from the overhead of transfering small amounts of data at once. Too large -// and the amount of data remaining in the buffer right before refilling it -// would be a buffer underflow risk. We set it to half of the buffer size. -static const int kPlaybackRequestFactor = 2; - -// Capture. - -// For capture, low latency is not a buffer overflow risk, but it makes us burn -// CPU from the overhead of transfering small amounts of data at once, so we set -// a recommended value that we use for the kLowLatency constant (but if the user -// explicitly requests something lower then we will honour it). -// 1ms takes about 6-7% CPU. 5ms takes about 5%. 10ms takes about 4.x%. -static const int kLowCaptureLatencyMsecs = 10; -// There is a round-trip delay to ack the data to the server, so the -// server-side buffer needs extra space to prevent buffer overflow. 20ms is -// sufficient, but there is no penalty to making it bigger, so we make it huge. -// (750ms is libpulse's default value for the _total_ buffer size in the -// kNoLatencyRequirements case.) -static const int kCaptureBufferExtraMsecs = 750; - -static void FillPlaybackBufferAttr(int latency, - pa_buffer_attr *attr) { - attr->maxlength = latency; - attr->tlength = latency; - attr->minreq = latency / kPlaybackRequestFactor; - attr->prebuf = attr->tlength - attr->minreq; - LOG(LS_VERBOSE) << "Configuring latency = " << attr->tlength << ", minreq = " - << attr->minreq << ", minfill = " << attr->prebuf; -} - -static pa_volume_t CricketVolumeToPulseVolume(int volume) { - // PA's volume space goes from 0% at PA_VOLUME_MUTED (value 0) to 100% at - // PA_VOLUME_NORM (value 0x10000). It can also go beyond 100% up to - // PA_VOLUME_MAX (value UINT32_MAX-1), but using that is probably unwise. - // We just linearly map the 0-255 scale of SoundSystemInterface onto - // PA_VOLUME_MUTED-PA_VOLUME_NORM. If the programmer exceeds kMaxVolume then - // they can access the over-100% features of PA. - return PA_VOLUME_MUTED + (PA_VOLUME_NORM - PA_VOLUME_MUTED) * - volume / SoundSystemInterface::kMaxVolume; -} - -static int PulseVolumeToCricketVolume(pa_volume_t pa_volume) { - return SoundSystemInterface::kMinVolume + - (SoundSystemInterface::kMaxVolume - SoundSystemInterface::kMinVolume) * - pa_volume / PA_VOLUME_NORM; -} - -static pa_volume_t MaxChannelVolume(pa_cvolume *channel_volumes) { - pa_volume_t pa_volume = PA_VOLUME_MUTED; // Minimum possible value. - for (int i = 0; i < channel_volumes->channels; ++i) { - if (pa_volume < channel_volumes->values[i]) { - pa_volume = channel_volumes->values[i]; - } - } - return pa_volume; -} - -class PulseAudioDeviceLocator : public SoundDeviceLocator { - public: - PulseAudioDeviceLocator(const std::string &name, - const std::string &device_name) - : SoundDeviceLocator(name, device_name) { - } - - virtual SoundDeviceLocator *Copy() const { - return new PulseAudioDeviceLocator(*this); - } -}; - -// Functionality that is common to both PulseAudioInputStream and -// PulseAudioOutputStream. -class PulseAudioStream { - public: - PulseAudioStream(PulseAudioSoundSystem *pulse, pa_stream *stream, int flags) - : pulse_(pulse), stream_(stream), flags_(flags) { - } - - ~PulseAudioStream() { - // Close() should have been called during the containing class's destructor. - ASSERT(stream_ == NULL); - } - - // Must be called with the lock held. - bool Close() { - if (!IsClosed()) { - // Unset this here so that we don't get a TERMINATED callback. - symbol_table()->pa_stream_set_state_callback()(stream_, NULL, NULL); - if (symbol_table()->pa_stream_disconnect()(stream_) != 0) { - LOG(LS_ERROR) << "Can't disconnect stream"; - // Continue and return true anyways. - } - symbol_table()->pa_stream_unref()(stream_); - stream_ = NULL; - } - return true; - } - - // Must be called with the lock held. - int LatencyUsecs() { - if (!(flags_ & SoundSystemInterface::FLAG_REPORT_LATENCY)) { - return 0; - } - - pa_usec_t latency; - int negative; - Lock(); - int re = symbol_table()->pa_stream_get_latency()(stream_, &latency, - &negative); - Unlock(); - if (re != 0) { - LOG(LS_ERROR) << "Can't query latency"; - // We'd rather continue playout/capture with an incorrect delay than stop - // it altogether, so return a valid value. - return 0; - } - if (negative) { - // The delay can be negative for monitoring streams if the captured - // samples haven't been played yet. In such a case, "latency" contains the - // magnitude, so we must negate it to get the real value. - return -latency; - } else { - return latency; - } - } - - PulseAudioSoundSystem *pulse() { - return pulse_; - } - - PulseAudioSymbolTable *symbol_table() { - return &pulse()->symbol_table_; - } - - pa_stream *stream() { - ASSERT(stream_ != NULL); - return stream_; - } - - bool IsClosed() { - return stream_ == NULL; - } - - void Lock() { - pulse()->Lock(); - } - - void Unlock() { - pulse()->Unlock(); - } - - private: - PulseAudioSoundSystem *pulse_; - pa_stream *stream_; - int flags_; - - DISALLOW_COPY_AND_ASSIGN(PulseAudioStream); -}; - -// Implementation of an input stream. See soundinputstreaminterface.h regarding -// thread-safety. -class PulseAudioInputStream : - public SoundInputStreamInterface, - private rtc::Worker { - - struct GetVolumeCallbackData { - PulseAudioInputStream *instance; - pa_cvolume *channel_volumes; - }; - - struct GetSourceChannelCountCallbackData { - PulseAudioInputStream *instance; - uint8_t *channels; - }; - - public: - PulseAudioInputStream(PulseAudioSoundSystem *pulse, - pa_stream *stream, - int flags) - : stream_(pulse, stream, flags), - temp_sample_data_(NULL), - temp_sample_data_size_(0) { - // This callback seems to never be issued, but let's set it anyways. - symbol_table()->pa_stream_set_overflow_callback()(stream, &OverflowCallback, - NULL); - } - - virtual ~PulseAudioInputStream() { - bool success = Close(); - // We need that to live. - VERIFY(success); - } - - virtual bool StartReading() { - return StartWork(); - } - - virtual bool StopReading() { - return StopWork(); - } - - virtual bool GetVolume(int *volume) { - bool ret = false; - - Lock(); - - // Unlike output streams, input streams have no concept of a stream volume, - // only a device volume. So we have to retrieve the volume of the device - // itself. - - pa_cvolume channel_volumes; - - GetVolumeCallbackData data; - data.instance = this; - data.channel_volumes = &channel_volumes; - - pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()( - stream_.pulse()->context_, - symbol_table()->pa_stream_get_device_index()(stream_.stream()), - &GetVolumeCallbackThunk, - &data); - if (!stream_.pulse()->FinishOperation(op)) { - goto done; - } - - if (data.channel_volumes) { - // This pointer was never unset by the callback, so we must have received - // an empty list of infos. This probably never happens, but we code for it - // anyway. - LOG(LS_ERROR) << "Did not receive GetVolumeCallback"; - goto done; - } - - // We now have the volume for each channel. Each channel could have a - // different volume if, e.g., the user went and changed the volumes in the - // PA UI. To get a single volume for SoundSystemInterface we just take the - // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in - // Hardy, so we do it manually. - pa_volume_t pa_volume; - pa_volume = MaxChannelVolume(&channel_volumes); - // Now map onto the SoundSystemInterface range. - *volume = PulseVolumeToCricketVolume(pa_volume); - - ret = true; - done: - Unlock(); - return ret; - } - - virtual bool SetVolume(int volume) { - bool ret = false; - pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume); - - Lock(); - - // Unlike output streams, input streams have no concept of a stream volume, - // only a device volume. So we have to change the volume of the device - // itself. - - // The device may have a different number of channels than the stream and - // their mapping may be different, so we don't want to use the channel count - // from our sample spec. We could use PA_CHANNELS_MAX to cover our bases, - // and the server allows that even if the device's channel count is lower, - // but some buggy PA clients don't like that (the pavucontrol on Hardy dies - // in an assert if the channel count is different). So instead we look up - // the actual number of channels that the device has. - - uint8_t channels; - - GetSourceChannelCountCallbackData data; - data.instance = this; - data.channels = &channels; - - uint32_t device_index = symbol_table()->pa_stream_get_device_index()( - stream_.stream()); - - pa_operation *op = symbol_table()->pa_context_get_source_info_by_index()( - stream_.pulse()->context_, - device_index, - &GetSourceChannelCountCallbackThunk, - &data); - if (!stream_.pulse()->FinishOperation(op)) { - goto done; - } - - if (data.channels) { - // This pointer was never unset by the callback, so we must have received - // an empty list of infos. This probably never happens, but we code for it - // anyway. - LOG(LS_ERROR) << "Did not receive GetSourceChannelCountCallback"; - goto done; - } - - pa_cvolume channel_volumes; - symbol_table()->pa_cvolume_set()(&channel_volumes, channels, pa_volume); - - op = symbol_table()->pa_context_set_source_volume_by_index()( - stream_.pulse()->context_, - device_index, - &channel_volumes, - // This callback merely logs errors. - &SetVolumeCallback, - NULL); - if (!op) { - LOG(LS_ERROR) << "pa_context_set_source_volume_by_index()"; - goto done; - } - // Don't need to wait for this to complete. - symbol_table()->pa_operation_unref()(op); - - ret = true; - done: - Unlock(); - return ret; - } - - virtual bool Close() { - if (!StopReading()) { - return false; - } - bool ret = true; - if (!stream_.IsClosed()) { - Lock(); - ret = stream_.Close(); - Unlock(); - } - return ret; - } - - virtual int LatencyUsecs() { - return stream_.LatencyUsecs(); - } - - private: - void Lock() { - stream_.Lock(); - } - - void Unlock() { - stream_.Unlock(); - } - - PulseAudioSymbolTable *symbol_table() { - return stream_.symbol_table(); - } - - void EnableReadCallback() { - symbol_table()->pa_stream_set_read_callback()( - stream_.stream(), - &ReadCallbackThunk, - this); - } - - void DisableReadCallback() { - symbol_table()->pa_stream_set_read_callback()( - stream_.stream(), - NULL, - NULL); - } - - static void ReadCallbackThunk(pa_stream *unused1, - size_t unused2, - void *userdata) { - PulseAudioInputStream *instance = - static_cast<PulseAudioInputStream *>(userdata); - instance->OnReadCallback(); - } - - void OnReadCallback() { - // We get the data pointer and size now in order to save one Lock/Unlock - // on OnMessage. - if (symbol_table()->pa_stream_peek()(stream_.stream(), - &temp_sample_data_, - &temp_sample_data_size_) != 0) { - LOG(LS_ERROR) << "Can't read data!"; - return; - } - // Since we consume the data asynchronously on a different thread, we have - // to temporarily disable the read callback or else Pulse will call it - // continuously until we consume the data. We re-enable it below. - DisableReadCallback(); - HaveWork(); - } - - // Inherited from Worker. - virtual void OnStart() { - Lock(); - EnableReadCallback(); - Unlock(); - } - - // Inherited from Worker. - virtual void OnHaveWork() { - ASSERT(temp_sample_data_ && temp_sample_data_size_); - SignalSamplesRead(temp_sample_data_, - temp_sample_data_size_, - this); - temp_sample_data_ = NULL; - temp_sample_data_size_ = 0; - - Lock(); - for (;;) { - // Ack the last thing we read. - if (symbol_table()->pa_stream_drop()(stream_.stream()) != 0) { - LOG(LS_ERROR) << "Can't ack read data"; - } - - if (symbol_table()->pa_stream_readable_size()(stream_.stream()) <= 0) { - // Then that was all the data. - break; - } - - // Else more data. - const void *sample_data; - size_t sample_data_size; - if (symbol_table()->pa_stream_peek()(stream_.stream(), - &sample_data, - &sample_data_size) != 0) { - LOG(LS_ERROR) << "Can't read data!"; - break; - } - - // Drop lock for sigslot dispatch, which could take a while. - Unlock(); - SignalSamplesRead(sample_data, sample_data_size, this); - Lock(); - - // Return to top of loop for the ack and the check for more data. - } - EnableReadCallback(); - Unlock(); - } - - // Inherited from Worker. - virtual void OnStop() { - Lock(); - DisableReadCallback(); - Unlock(); - } - - static void OverflowCallback(pa_stream *stream, - void *userdata) { - LOG(LS_WARNING) << "Buffer overflow on capture stream " << stream; - } - - static void GetVolumeCallbackThunk(pa_context *unused, - const pa_source_info *info, - int eol, - void *userdata) { - GetVolumeCallbackData *data = - static_cast<GetVolumeCallbackData *>(userdata); - data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes); - } - - void OnGetVolumeCallback(const pa_source_info *info, - int eol, - pa_cvolume **channel_volumes) { - if (eol) { - // List is over. Wake GetVolume(). - stream_.pulse()->Signal(); - return; - } - - if (*channel_volumes) { - **channel_volumes = info->volume; - // Unset the pointer so that we know that we have have already copied the - // volume. - *channel_volumes = NULL; - } else { - // We have received an additional callback after the first one, which - // doesn't make sense for a single source. This probably never happens, - // but we code for it anyway. - LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback"; - } - } - - static void GetSourceChannelCountCallbackThunk(pa_context *unused, - const pa_source_info *info, - int eol, - void *userdata) { - GetSourceChannelCountCallbackData *data = - static_cast<GetSourceChannelCountCallbackData *>(userdata); - data->instance->OnGetSourceChannelCountCallback(info, eol, &data->channels); - } - - void OnGetSourceChannelCountCallback(const pa_source_info *info, - int eol, - uint8_t **channels) { - if (eol) { - // List is over. Wake SetVolume(). - stream_.pulse()->Signal(); - return; - } - - if (*channels) { - **channels = info->channel_map.channels; - // Unset the pointer so that we know that we have have already copied the - // channel count. - *channels = NULL; - } else { - // We have received an additional callback after the first one, which - // doesn't make sense for a single source. This probably never happens, - // but we code for it anyway. - LOG(LS_WARNING) << "Ignoring extra GetSourceChannelCountCallback"; - } - } - - static void SetVolumeCallback(pa_context *unused1, - int success, - void *unused2) { - if (!success) { - LOG(LS_ERROR) << "Failed to change capture volume"; - } - } - - PulseAudioStream stream_; - // Temporary storage for passing data between threads. - const void *temp_sample_data_; - size_t temp_sample_data_size_; - - DISALLOW_COPY_AND_ASSIGN(PulseAudioInputStream); -}; - -// Implementation of an output stream. See soundoutputstreaminterface.h -// regarding thread-safety. -class PulseAudioOutputStream : - public SoundOutputStreamInterface, - private rtc::Worker { - - struct GetVolumeCallbackData { - PulseAudioOutputStream *instance; - pa_cvolume *channel_volumes; - }; - - public: - PulseAudioOutputStream(PulseAudioSoundSystem *pulse, - pa_stream *stream, - int flags, - int latency) - : stream_(pulse, stream, flags), - configured_latency_(latency), - temp_buffer_space_(0) { - symbol_table()->pa_stream_set_underflow_callback()(stream, - &UnderflowCallbackThunk, - this); - } - - virtual ~PulseAudioOutputStream() { - bool success = Close(); - // We need that to live. - VERIFY(success); - } - - virtual bool EnableBufferMonitoring() { - return StartWork(); - } - - virtual bool DisableBufferMonitoring() { - return StopWork(); - } - - virtual bool WriteSamples(const void *sample_data, - size_t size) { - bool ret = true; - Lock(); - if (symbol_table()->pa_stream_write()(stream_.stream(), - sample_data, - size, - NULL, - 0, - PA_SEEK_RELATIVE) != 0) { - LOG(LS_ERROR) << "Unable to write"; - ret = false; - } - Unlock(); - return ret; - } - - virtual bool GetVolume(int *volume) { - bool ret = false; - - Lock(); - - pa_cvolume channel_volumes; - - GetVolumeCallbackData data; - data.instance = this; - data.channel_volumes = &channel_volumes; - - pa_operation *op = symbol_table()->pa_context_get_sink_input_info()( - stream_.pulse()->context_, - symbol_table()->pa_stream_get_index()(stream_.stream()), - &GetVolumeCallbackThunk, - &data); - if (!stream_.pulse()->FinishOperation(op)) { - goto done; - } - - if (data.channel_volumes) { - // This pointer was never unset by the callback, so we must have received - // an empty list of infos. This probably never happens, but we code for it - // anyway. - LOG(LS_ERROR) << "Did not receive GetVolumeCallback"; - goto done; - } - - // We now have the volume for each channel. Each channel could have a - // different volume if, e.g., the user went and changed the volumes in the - // PA UI. To get a single volume for SoundSystemInterface we just take the - // maximum. Ideally we'd do so with pa_cvolume_max, but it doesn't exist in - // Hardy, so we do it manually. - pa_volume_t pa_volume; - pa_volume = MaxChannelVolume(&channel_volumes); - // Now map onto the SoundSystemInterface range. - *volume = PulseVolumeToCricketVolume(pa_volume); - - ret = true; - done: - Unlock(); - return ret; - } - - virtual bool SetVolume(int volume) { - bool ret = false; - pa_volume_t pa_volume = CricketVolumeToPulseVolume(volume); - - Lock(); - - const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()( - stream_.stream()); - if (!spec) { - LOG(LS_ERROR) << "pa_stream_get_sample_spec()"; - goto done; - } - - pa_cvolume channel_volumes; - symbol_table()->pa_cvolume_set()(&channel_volumes, spec->channels, - pa_volume); - - pa_operation *op; - op = symbol_table()->pa_context_set_sink_input_volume()( - stream_.pulse()->context_, - symbol_table()->pa_stream_get_index()(stream_.stream()), - &channel_volumes, - // This callback merely logs errors. - &SetVolumeCallback, - NULL); - if (!op) { - LOG(LS_ERROR) << "pa_context_set_sink_input_volume()"; - goto done; - } - // Don't need to wait for this to complete. - symbol_table()->pa_operation_unref()(op); - - ret = true; - done: - Unlock(); - return ret; - } - - virtual bool Close() { - if (!DisableBufferMonitoring()) { - return false; - } - bool ret = true; - if (!stream_.IsClosed()) { - Lock(); - symbol_table()->pa_stream_set_underflow_callback()(stream_.stream(), - NULL, - NULL); - ret = stream_.Close(); - Unlock(); - } - return ret; - } - - virtual int LatencyUsecs() { - return stream_.LatencyUsecs(); - } - -#if 0 - // TODO: Versions 0.9.16 and later of Pulse have a new API for - // zero-copy writes, but Hardy is not new enough to have that so we can't - // rely on it. Perhaps auto-detect if it's present or not and use it if we - // can? - - virtual bool GetWriteBuffer(void **buffer, size_t *size) { - bool ret = true; - Lock(); - if (symbol_table()->pa_stream_begin_write()(stream_.stream(), buffer, size) - != 0) { - LOG(LS_ERROR) << "Can't get write buffer"; - ret = false; - } - Unlock(); - return ret; - } - - // Releases the caller's hold on the write buffer. "written" must be the - // amount of data that was written. - virtual bool ReleaseWriteBuffer(void *buffer, size_t written) { - bool ret = true; - Lock(); - if (written == 0) { - if (symbol_table()->pa_stream_cancel_write()(stream_.stream()) != 0) { - LOG(LS_ERROR) << "Can't cancel write"; - ret = false; - } - } else { - if (symbol_table()->pa_stream_write()(stream_.stream(), - buffer, - written, - NULL, - 0, - PA_SEEK_RELATIVE) != 0) { - LOG(LS_ERROR) << "Unable to write"; - ret = false; - } - } - Unlock(); - return ret; - } -#endif - - private: - void Lock() { - stream_.Lock(); - } - - void Unlock() { - stream_.Unlock(); - } - - PulseAudioSymbolTable *symbol_table() { - return stream_.symbol_table(); - } - - void EnableWriteCallback() { - pa_stream_state_t state = symbol_table()->pa_stream_get_state()( - stream_.stream()); - if (state == PA_STREAM_READY) { - // May already have available space. Must check. - temp_buffer_space_ = symbol_table()->pa_stream_writable_size()( - stream_.stream()); - if (temp_buffer_space_ > 0) { - // Yup, there is already space available, so if we register a write - // callback then it will not receive any event. So dispatch one ourself - // instead. - HaveWork(); - return; - } - } - symbol_table()->pa_stream_set_write_callback()( - stream_.stream(), - &WriteCallbackThunk, - this); - } - - void DisableWriteCallback() { - symbol_table()->pa_stream_set_write_callback()( - stream_.stream(), - NULL, - NULL); - } - - static void WriteCallbackThunk(pa_stream *unused, - size_t buffer_space, - void *userdata) { - PulseAudioOutputStream *instance = - static_cast<PulseAudioOutputStream *>(userdata); - instance->OnWriteCallback(buffer_space); - } - - void OnWriteCallback(size_t buffer_space) { - temp_buffer_space_ = buffer_space; - // Since we write the data asynchronously on a different thread, we have - // to temporarily disable the write callback or else Pulse will call it - // continuously until we write the data. We re-enable it below. - DisableWriteCallback(); - HaveWork(); - } - - // Inherited from Worker. - virtual void OnStart() { - Lock(); - EnableWriteCallback(); - Unlock(); - } - - // Inherited from Worker. - virtual void OnHaveWork() { - ASSERT(temp_buffer_space_ > 0); - - SignalBufferSpace(temp_buffer_space_, this); - - temp_buffer_space_ = 0; - Lock(); - EnableWriteCallback(); - Unlock(); - } - - // Inherited from Worker. - virtual void OnStop() { - Lock(); - DisableWriteCallback(); - Unlock(); - } - - static void UnderflowCallbackThunk(pa_stream *unused, - void *userdata) { - PulseAudioOutputStream *instance = - static_cast<PulseAudioOutputStream *>(userdata); - instance->OnUnderflowCallback(); - } - - void OnUnderflowCallback() { - LOG(LS_WARNING) << "Buffer underflow on playback stream " - << stream_.stream(); - - if (configured_latency_ == SoundSystemInterface::kNoLatencyRequirements) { - // We didn't configure a pa_buffer_attr before, so switching to one now - // would be questionable. - return; - } - - // Otherwise reconfigure the stream with a higher target latency. - - const pa_sample_spec *spec = symbol_table()->pa_stream_get_sample_spec()( - stream_.stream()); - if (!spec) { - LOG(LS_ERROR) << "pa_stream_get_sample_spec()"; - return; - } - - size_t bytes_per_sec = symbol_table()->pa_bytes_per_second()(spec); - - int new_latency = configured_latency_ + - bytes_per_sec * kPlaybackLatencyIncrementMsecs / - rtc::kNumMicrosecsPerSec; - - pa_buffer_attr new_attr = {0}; - FillPlaybackBufferAttr(new_latency, &new_attr); - - pa_operation *op = symbol_table()->pa_stream_set_buffer_attr()( - stream_.stream(), - &new_attr, - // No callback. - NULL, - NULL); - if (!op) { - LOG(LS_ERROR) << "pa_stream_set_buffer_attr()"; - return; - } - // Don't need to wait for this to complete. - symbol_table()->pa_operation_unref()(op); - - // Save the new latency in case we underflow again. - configured_latency_ = new_latency; - } - - static void GetVolumeCallbackThunk(pa_context *unused, - const pa_sink_input_info *info, - int eol, - void *userdata) { - GetVolumeCallbackData *data = - static_cast<GetVolumeCallbackData *>(userdata); - data->instance->OnGetVolumeCallback(info, eol, &data->channel_volumes); - } - - void OnGetVolumeCallback(const pa_sink_input_info *info, - int eol, - pa_cvolume **channel_volumes) { - if (eol) { - // List is over. Wake GetVolume(). - stream_.pulse()->Signal(); - return; - } - - if (*channel_volumes) { - **channel_volumes = info->volume; - // Unset the pointer so that we know that we have have already copied the - // volume. - *channel_volumes = NULL; - } else { - // We have received an additional callback after the first one, which - // doesn't make sense for a single sink input. This probably never - // happens, but we code for it anyway. - LOG(LS_WARNING) << "Ignoring extra GetVolumeCallback"; - } - } - - static void SetVolumeCallback(pa_context *unused1, - int success, - void *unused2) { - if (!success) { - LOG(LS_ERROR) << "Failed to change playback volume"; - } - } - - PulseAudioStream stream_; - int configured_latency_; - // Temporary storage for passing data between threads. - size_t temp_buffer_space_; - - DISALLOW_COPY_AND_ASSIGN(PulseAudioOutputStream); -}; - -PulseAudioSoundSystem::PulseAudioSoundSystem() - : mainloop_(NULL), context_(NULL) { -} - -PulseAudioSoundSystem::~PulseAudioSoundSystem() { - Terminate(); -} - -bool PulseAudioSoundSystem::Init() { - if (IsInitialized()) { - return true; - } - - // Load libpulse. - if (!symbol_table_.Load()) { - // Most likely the Pulse library and sound server are not installed on - // this system. - LOG(LS_WARNING) << "Failed to load symbol table"; - return false; - } - - // Now create and start the Pulse event thread. - mainloop_ = symbol_table_.pa_threaded_mainloop_new()(); - if (!mainloop_) { - LOG(LS_ERROR) << "Can't create mainloop"; - goto fail0; - } - - if (symbol_table_.pa_threaded_mainloop_start()(mainloop_) != 0) { - LOG(LS_ERROR) << "Can't start mainloop"; - goto fail1; - } - - Lock(); - context_ = CreateNewConnection(); - Unlock(); - - if (!context_) { - goto fail2; - } - - // Otherwise we're now ready! - return true; - - fail2: - symbol_table_.pa_threaded_mainloop_stop()(mainloop_); - fail1: - symbol_table_.pa_threaded_mainloop_free()(mainloop_); - mainloop_ = NULL; - fail0: - return false; -} - -void PulseAudioSoundSystem::Terminate() { - if (!IsInitialized()) { - return; - } - - Lock(); - symbol_table_.pa_context_disconnect()(context_); - symbol_table_.pa_context_unref()(context_); - Unlock(); - context_ = NULL; - symbol_table_.pa_threaded_mainloop_stop()(mainloop_); - symbol_table_.pa_threaded_mainloop_free()(mainloop_); - mainloop_ = NULL; - - // We do not unload the symbol table because we may need it again soon if - // Init() is called again. -} - -bool PulseAudioSoundSystem::EnumeratePlaybackDevices( - SoundDeviceLocatorList *devices) { - return EnumerateDevices<pa_sink_info>( - devices, - symbol_table_.pa_context_get_sink_info_list(), - &EnumeratePlaybackDevicesCallbackThunk); -} - -bool PulseAudioSoundSystem::EnumerateCaptureDevices( - SoundDeviceLocatorList *devices) { - return EnumerateDevices<pa_source_info>( - devices, - symbol_table_.pa_context_get_source_info_list(), - &EnumerateCaptureDevicesCallbackThunk); -} - -bool PulseAudioSoundSystem::GetDefaultPlaybackDevice( - SoundDeviceLocator **device) { - return GetDefaultDevice<&pa_server_info::default_sink_name>(device); -} - -bool PulseAudioSoundSystem::GetDefaultCaptureDevice( - SoundDeviceLocator **device) { - return GetDefaultDevice<&pa_server_info::default_source_name>(device); -} - -SoundOutputStreamInterface *PulseAudioSoundSystem::OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return OpenDevice<SoundOutputStreamInterface>( - device, - params, - "Playback", - &PulseAudioSoundSystem::ConnectOutputStream); -} - -SoundInputStreamInterface *PulseAudioSoundSystem::OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return OpenDevice<SoundInputStreamInterface>( - device, - params, - "Capture", - &PulseAudioSoundSystem::ConnectInputStream); -} - -const char *PulseAudioSoundSystem::GetName() const { - return "PulseAudio"; -} - -inline bool PulseAudioSoundSystem::IsInitialized() { - return mainloop_ != NULL; -} - -struct ConnectToPulseCallbackData { - PulseAudioSoundSystem *instance; - bool connect_done; -}; - -void PulseAudioSoundSystem::ConnectToPulseCallbackThunk( - pa_context *context, void *userdata) { - ConnectToPulseCallbackData *data = - static_cast<ConnectToPulseCallbackData *>(userdata); - data->instance->OnConnectToPulseCallback(context, &data->connect_done); -} - -void PulseAudioSoundSystem::OnConnectToPulseCallback( - pa_context *context, bool *connect_done) { - pa_context_state_t state = symbol_table_.pa_context_get_state()(context); - if (state == PA_CONTEXT_READY || - state == PA_CONTEXT_FAILED || - state == PA_CONTEXT_TERMINATED) { - // Connection process has reached a terminal state. Wake ConnectToPulse(). - *connect_done = true; - Signal(); - } -} - -// Must be called with the lock held. -bool PulseAudioSoundSystem::ConnectToPulse(pa_context *context) { - bool ret = true; - ConnectToPulseCallbackData data; - // Have to put this up here to satisfy the compiler. - pa_context_state_t state; - - data.instance = this; - data.connect_done = false; - - symbol_table_.pa_context_set_state_callback()(context, - &ConnectToPulseCallbackThunk, - &data); - - // Connect to PulseAudio sound server. - if (symbol_table_.pa_context_connect()( - context, - NULL, // Default server - PA_CONTEXT_NOAUTOSPAWN, - NULL) != 0) { // No special fork handling needed - LOG(LS_ERROR) << "Can't start connection to PulseAudio sound server"; - ret = false; - goto done; - } - - // Wait for the connection state machine to reach a terminal state. - do { - Wait(); - } while (!data.connect_done); - - // Now check to see what final state we reached. - state = symbol_table_.pa_context_get_state()(context); - - if (state != PA_CONTEXT_READY) { - if (state == PA_CONTEXT_FAILED) { - LOG(LS_ERROR) << "Failed to connect to PulseAudio sound server"; - } else if (state == PA_CONTEXT_TERMINATED) { - LOG(LS_ERROR) << "PulseAudio connection terminated early"; - } else { - // Shouldn't happen, because we only signal on one of those three states. - LOG(LS_ERROR) << "Unknown problem connecting to PulseAudio"; - } - ret = false; - } - - done: - // We unset our callback for safety just in case the state might somehow - // change later, because the pointer to "data" will be invalid after return - // from this function. - symbol_table_.pa_context_set_state_callback()(context, NULL, NULL); - return ret; -} - -// Must be called with the lock held. -pa_context *PulseAudioSoundSystem::CreateNewConnection() { - // Create connection context. - std::string app_name; - // TODO: Pulse etiquette says this name should be localized. Do - // we care? - rtc::Filesystem::GetApplicationName(&app_name); - pa_context *context = symbol_table_.pa_context_new()( - symbol_table_.pa_threaded_mainloop_get_api()(mainloop_), - app_name.c_str()); - if (!context) { - LOG(LS_ERROR) << "Can't create context"; - goto fail0; - } - - // Now connect. - if (!ConnectToPulse(context)) { - goto fail1; - } - - // Otherwise the connection succeeded and is ready. - return context; - - fail1: - symbol_table_.pa_context_unref()(context); - fail0: - return NULL; -} - -struct EnumerateDevicesCallbackData { - PulseAudioSoundSystem *instance; - SoundSystemInterface::SoundDeviceLocatorList *devices; -}; - -void PulseAudioSoundSystem::EnumeratePlaybackDevicesCallbackThunk( - pa_context *unused, - const pa_sink_info *info, - int eol, - void *userdata) { - EnumerateDevicesCallbackData *data = - static_cast<EnumerateDevicesCallbackData *>(userdata); - data->instance->OnEnumeratePlaybackDevicesCallback(data->devices, info, eol); -} - -void PulseAudioSoundSystem::EnumerateCaptureDevicesCallbackThunk( - pa_context *unused, - const pa_source_info *info, - int eol, - void *userdata) { - EnumerateDevicesCallbackData *data = - static_cast<EnumerateDevicesCallbackData *>(userdata); - data->instance->OnEnumerateCaptureDevicesCallback(data->devices, info, eol); -} - -void PulseAudioSoundSystem::OnEnumeratePlaybackDevicesCallback( - SoundDeviceLocatorList *devices, - const pa_sink_info *info, - int eol) { - if (eol) { - // List is over. Wake EnumerateDevices(). - Signal(); - return; - } - - // Else this is the next device. - devices->push_back( - new PulseAudioDeviceLocator(info->description, info->name)); -} - -void PulseAudioSoundSystem::OnEnumerateCaptureDevicesCallback( - SoundDeviceLocatorList *devices, - const pa_source_info *info, - int eol) { - if (eol) { - // List is over. Wake EnumerateDevices(). - Signal(); - return; - } - - if (info->monitor_of_sink != PA_INVALID_INDEX) { - // We don't want to list monitor sources, since they are almost certainly - // not what the user wants for voice conferencing. - return; - } - - // Else this is the next device. - devices->push_back( - new PulseAudioDeviceLocator(info->description, info->name)); -} - -template <typename InfoStruct> -bool PulseAudioSoundSystem::EnumerateDevices( - SoundDeviceLocatorList *devices, - pa_operation *(*enumerate_fn)( - pa_context *c, - void (*callback_fn)( - pa_context *c, - const InfoStruct *i, - int eol, - void *userdata), - void *userdata), - void (*callback_fn)( - pa_context *c, - const InfoStruct *i, - int eol, - void *userdata)) { - ClearSoundDeviceLocatorList(devices); - if (!IsInitialized()) { - return false; - } - - EnumerateDevicesCallbackData data; - data.instance = this; - data.devices = devices; - - Lock(); - pa_operation *op = (*enumerate_fn)( - context_, - callback_fn, - &data); - bool ret = FinishOperation(op); - Unlock(); - return ret; -} - -struct GetDefaultDeviceCallbackData { - PulseAudioSoundSystem *instance; - SoundDeviceLocator **device; -}; - -template <const char *(pa_server_info::*field)> -void PulseAudioSoundSystem::GetDefaultDeviceCallbackThunk( - pa_context *unused, - const pa_server_info *info, - void *userdata) { - GetDefaultDeviceCallbackData *data = - static_cast<GetDefaultDeviceCallbackData *>(userdata); - data->instance->OnGetDefaultDeviceCallback<field>(info, data->device); -} - -template <const char *(pa_server_info::*field)> -void PulseAudioSoundSystem::OnGetDefaultDeviceCallback( - const pa_server_info *info, - SoundDeviceLocator **device) { - if (info) { - const char *dev = info->*field; - if (dev) { - *device = new PulseAudioDeviceLocator("Default device", dev); - } - } - Signal(); -} - -template <const char *(pa_server_info::*field)> -bool PulseAudioSoundSystem::GetDefaultDevice(SoundDeviceLocator **device) { - if (!IsInitialized()) { - return false; - } - bool ret; - *device = NULL; - GetDefaultDeviceCallbackData data; - data.instance = this; - data.device = device; - Lock(); - pa_operation *op = symbol_table_.pa_context_get_server_info()( - context_, - &GetDefaultDeviceCallbackThunk<field>, - &data); - ret = FinishOperation(op); - Unlock(); - return ret && (*device != NULL); -} - -void PulseAudioSoundSystem::StreamStateChangedCallbackThunk( - pa_stream *stream, - void *userdata) { - PulseAudioSoundSystem *instance = - static_cast<PulseAudioSoundSystem *>(userdata); - instance->OnStreamStateChangedCallback(stream); -} - -void PulseAudioSoundSystem::OnStreamStateChangedCallback(pa_stream *stream) { - pa_stream_state_t state = symbol_table_.pa_stream_get_state()(stream); - if (state == PA_STREAM_READY) { - LOG(LS_INFO) << "Pulse stream " << stream << " ready"; - } else if (state == PA_STREAM_FAILED || - state == PA_STREAM_TERMINATED || - state == PA_STREAM_UNCONNECTED) { - LOG(LS_ERROR) << "Pulse stream " << stream << " failed to connect: " - << LastError(); - } -} - -template <typename StreamInterface> -StreamInterface *PulseAudioSoundSystem::OpenDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms, - const char *stream_name, - StreamInterface *(PulseAudioSoundSystem::*connect_fn)( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec)) { - if (!IsInitialized()) { - return NULL; - } - - const char *dev = static_cast<const PulseAudioDeviceLocator *>(device)-> - device_name().c_str(); - - StreamInterface *stream_interface = NULL; - - ASSERT(params.format < ARRAY_SIZE(kCricketFormatToPulseFormatTable)); - - pa_sample_spec spec; - spec.format = kCricketFormatToPulseFormatTable[params.format]; - spec.rate = params.freq; - spec.channels = params.channels; - - int pa_flags = 0; - if (params.flags & FLAG_REPORT_LATENCY) { - pa_flags |= PA_STREAM_INTERPOLATE_TIMING | - PA_STREAM_AUTO_TIMING_UPDATE; - } - - if (params.latency != kNoLatencyRequirements) { - // If configuring a specific latency then we want to specify - // PA_STREAM_ADJUST_LATENCY to make the server adjust parameters - // automatically to reach that target latency. However, that flag doesn't - // exist in Ubuntu 8.04 and many people still use that, so we have to check - // the protocol version of libpulse. - if (symbol_table_.pa_context_get_protocol_version()(context_) >= - kAdjustLatencyProtocolVersion) { - pa_flags |= PA_STREAM_ADJUST_LATENCY; - } - } - - Lock(); - - pa_stream *stream = symbol_table_.pa_stream_new()(context_, stream_name, - &spec, NULL); - if (!stream) { - LOG(LS_ERROR) << "Can't create pa_stream"; - goto done; - } - - // Set a state callback to log errors. - symbol_table_.pa_stream_set_state_callback()(stream, - &StreamStateChangedCallbackThunk, - this); - - stream_interface = (this->*connect_fn)( - stream, - dev, - params.flags, - static_cast<pa_stream_flags_t>(pa_flags), - params.latency, - spec); - if (!stream_interface) { - LOG(LS_ERROR) << "Can't connect stream to " << dev; - symbol_table_.pa_stream_unref()(stream); - } - - done: - Unlock(); - return stream_interface; -} - -// Must be called with the lock held. -SoundOutputStreamInterface *PulseAudioSoundSystem::ConnectOutputStream( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec) { - pa_buffer_attr attr = {0}; - pa_buffer_attr *pattr = NULL; - if (latency != kNoLatencyRequirements) { - // kLowLatency is 0, so we treat it the same as a request for zero latency. - ssize_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec); - latency = rtc::_max( - latency, - static_cast<int>( - bytes_per_sec * kPlaybackLatencyMinimumMsecs / - rtc::kNumMicrosecsPerSec)); - FillPlaybackBufferAttr(latency, &attr); - pattr = &attr; - } - if (symbol_table_.pa_stream_connect_playback()( - stream, - dev, - pattr, - pa_flags, - // Let server choose volume - NULL, - // Not synchronized to any other playout - NULL) != 0) { - return NULL; - } - return new PulseAudioOutputStream(this, stream, flags, latency); -} - -// Must be called with the lock held. -SoundInputStreamInterface *PulseAudioSoundSystem::ConnectInputStream( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec) { - pa_buffer_attr attr = {0}; - pa_buffer_attr *pattr = NULL; - if (latency != kNoLatencyRequirements) { - size_t bytes_per_sec = symbol_table_.pa_bytes_per_second()(&spec); - if (latency == kLowLatency) { - latency = bytes_per_sec * kLowCaptureLatencyMsecs / - rtc::kNumMicrosecsPerSec; - } - // Note: fragsize specifies a maximum transfer size, not a minimum, so it is - // not possible to force a high latency setting, only a low one. - attr.fragsize = latency; - attr.maxlength = latency + bytes_per_sec * kCaptureBufferExtraMsecs / - rtc::kNumMicrosecsPerSec; - LOG(LS_VERBOSE) << "Configuring latency = " << attr.fragsize - << ", maxlength = " << attr.maxlength; - pattr = &attr; - } - if (symbol_table_.pa_stream_connect_record()(stream, - dev, - pattr, - pa_flags) != 0) { - return NULL; - } - return new PulseAudioInputStream(this, stream, flags); -} - -// Must be called with the lock held. -bool PulseAudioSoundSystem::FinishOperation(pa_operation *op) { - if (!op) { - LOG(LS_ERROR) << "Failed to start operation"; - return false; - } - - do { - Wait(); - } while (symbol_table_.pa_operation_get_state()(op) == PA_OPERATION_RUNNING); - - symbol_table_.pa_operation_unref()(op); - - return true; -} - -inline void PulseAudioSoundSystem::Lock() { - symbol_table_.pa_threaded_mainloop_lock()(mainloop_); -} - -inline void PulseAudioSoundSystem::Unlock() { - symbol_table_.pa_threaded_mainloop_unlock()(mainloop_); -} - -// Must be called with the lock held. -inline void PulseAudioSoundSystem::Wait() { - symbol_table_.pa_threaded_mainloop_wait()(mainloop_); -} - -// Must be called with the lock held. -inline void PulseAudioSoundSystem::Signal() { - symbol_table_.pa_threaded_mainloop_signal()(mainloop_, 0); -} - -// Must be called with the lock held. -const char *PulseAudioSoundSystem::LastError() { - return symbol_table_.pa_strerror()(symbol_table_.pa_context_errno()( - context_)); -} - -} // namespace cricket - -#endif // HAVE_LIBPULSE diff --git a/sound/pulseaudiosoundsystem.h b/sound/pulseaudiosoundsystem.h deleted file mode 100644 index 16938d6..0000000 --- a/sound/pulseaudiosoundsystem.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_PULSEAUDIOSOUNDSYSTEM_H_ -#define TALK_SOUND_PULSEAUDIOSOUNDSYSTEM_H_ - -#ifdef HAVE_LIBPULSE - -#include "talk/sound/pulseaudiosymboltable.h" -#include "talk/sound/soundsysteminterface.h" -#include "webrtc/base/constructormagic.h" - -namespace cricket { - -class PulseAudioInputStream; -class PulseAudioOutputStream; -class PulseAudioStream; - -// Sound system implementation for PulseAudio, a cross-platform sound server -// (but commonly used only on Linux, which is the only platform we support -// it on). -// Init(), Terminate(), and the destructor should never be invoked concurrently, -// but all other methods are thread-safe. -class PulseAudioSoundSystem : public SoundSystemInterface { - friend class PulseAudioInputStream; - friend class PulseAudioOutputStream; - friend class PulseAudioStream; - public: - static SoundSystemInterface *Create() { - return new PulseAudioSoundSystem(); - } - - PulseAudioSoundSystem(); - - virtual ~PulseAudioSoundSystem(); - - virtual bool Init(); - virtual void Terminate(); - - virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices); - virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices); - - virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device); - virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device); - - virtual SoundOutputStreamInterface *OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - virtual SoundInputStreamInterface *OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - - virtual const char *GetName() const; - - private: - bool IsInitialized(); - - static void ConnectToPulseCallbackThunk(pa_context *context, void *userdata); - - void OnConnectToPulseCallback(pa_context *context, bool *connect_done); - - bool ConnectToPulse(pa_context *context); - - pa_context *CreateNewConnection(); - - template <typename InfoStruct> - bool EnumerateDevices(SoundDeviceLocatorList *devices, - pa_operation *(*enumerate_fn)( - pa_context *c, - void (*callback_fn)( - pa_context *c, - const InfoStruct *i, - int eol, - void *userdata), - void *userdata), - void (*callback_fn)( - pa_context *c, - const InfoStruct *i, - int eol, - void *userdata)); - - static void EnumeratePlaybackDevicesCallbackThunk(pa_context *unused, - const pa_sink_info *info, - int eol, - void *userdata); - - static void EnumerateCaptureDevicesCallbackThunk(pa_context *unused, - const pa_source_info *info, - int eol, - void *userdata); - - void OnEnumeratePlaybackDevicesCallback( - SoundDeviceLocatorList *devices, - const pa_sink_info *info, - int eol); - - void OnEnumerateCaptureDevicesCallback( - SoundDeviceLocatorList *devices, - const pa_source_info *info, - int eol); - - template <const char *(pa_server_info::*field)> - static void GetDefaultDeviceCallbackThunk( - pa_context *unused, - const pa_server_info *info, - void *userdata); - - template <const char *(pa_server_info::*field)> - void OnGetDefaultDeviceCallback( - const pa_server_info *info, - SoundDeviceLocator **device); - - template <const char *(pa_server_info::*field)> - bool GetDefaultDevice(SoundDeviceLocator **device); - - static void StreamStateChangedCallbackThunk(pa_stream *stream, - void *userdata); - - void OnStreamStateChangedCallback(pa_stream *stream); - - template <typename StreamInterface> - StreamInterface *OpenDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms, - const char *stream_name, - StreamInterface *(PulseAudioSoundSystem::*connect_fn)( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec)); - - SoundOutputStreamInterface *ConnectOutputStream( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec); - - SoundInputStreamInterface *ConnectInputStream( - pa_stream *stream, - const char *dev, - int flags, - pa_stream_flags_t pa_flags, - int latency, - const pa_sample_spec &spec); - - bool FinishOperation(pa_operation *op); - - void Lock(); - void Unlock(); - void Wait(); - void Signal(); - - const char *LastError(); - - pa_threaded_mainloop *mainloop_; - pa_context *context_; - PulseAudioSymbolTable symbol_table_; - - DISALLOW_COPY_AND_ASSIGN(PulseAudioSoundSystem); -}; - -} // namespace cricket - -#endif // HAVE_LIBPULSE - -#endif // TALK_SOUND_PULSEAUDIOSOUNDSYSTEM_H_ diff --git a/sound/pulseaudiosymboltable.cc b/sound/pulseaudiosymboltable.cc deleted file mode 100644 index 344f354..0000000 --- a/sound/pulseaudiosymboltable.cc +++ /dev/null @@ -1,41 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifdef HAVE_LIBPULSE - -#include "talk/sound/pulseaudiosymboltable.h" - -namespace cricket { - -#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME PULSE_AUDIO_SYMBOLS_CLASS_NAME -#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST PULSE_AUDIO_SYMBOLS_LIST -#define LATE_BINDING_SYMBOL_TABLE_DLL_NAME "libpulse.so.0" -#include "webrtc/base/latebindingsymboltable.cc.def" - -} // namespace cricket - -#endif // HAVE_LIBPULSE diff --git a/sound/pulseaudiosymboltable.h b/sound/pulseaudiosymboltable.h deleted file mode 100644 index 46bddea..0000000 --- a/sound/pulseaudiosymboltable.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_PULSEAUDIOSYMBOLTABLE_H_ -#define TALK_SOUND_PULSEAUDIOSYMBOLTABLE_H_ - -#include <pulse/context.h> -#include <pulse/def.h> -#include <pulse/error.h> -#include <pulse/introspect.h> -#include <pulse/stream.h> -#include <pulse/thread-mainloop.h> - -#include "webrtc/base/latebindingsymboltable.h" - -namespace cricket { - -#define PULSE_AUDIO_SYMBOLS_CLASS_NAME PulseAudioSymbolTable -// The PulseAudio symbols we need, as an X-Macro list. -// This list must contain precisely every libpulse function that is used in -// pulseaudiosoundsystem.cc. -#define PULSE_AUDIO_SYMBOLS_LIST \ - X(pa_bytes_per_second) \ - X(pa_context_connect) \ - X(pa_context_disconnect) \ - X(pa_context_errno) \ - X(pa_context_get_protocol_version) \ - X(pa_context_get_server_info) \ - X(pa_context_get_sink_info_list) \ - X(pa_context_get_sink_input_info) \ - X(pa_context_get_source_info_by_index) \ - X(pa_context_get_source_info_list) \ - X(pa_context_get_state) \ - X(pa_context_new) \ - X(pa_context_set_sink_input_volume) \ - X(pa_context_set_source_volume_by_index) \ - X(pa_context_set_state_callback) \ - X(pa_context_unref) \ - X(pa_cvolume_set) \ - X(pa_operation_get_state) \ - X(pa_operation_unref) \ - X(pa_stream_connect_playback) \ - X(pa_stream_connect_record) \ - X(pa_stream_disconnect) \ - X(pa_stream_drop) \ - X(pa_stream_get_device_index) \ - X(pa_stream_get_index) \ - X(pa_stream_get_latency) \ - X(pa_stream_get_sample_spec) \ - X(pa_stream_get_state) \ - X(pa_stream_new) \ - X(pa_stream_peek) \ - X(pa_stream_readable_size) \ - X(pa_stream_set_buffer_attr) \ - X(pa_stream_set_overflow_callback) \ - X(pa_stream_set_read_callback) \ - X(pa_stream_set_state_callback) \ - X(pa_stream_set_underflow_callback) \ - X(pa_stream_set_write_callback) \ - X(pa_stream_unref) \ - X(pa_stream_writable_size) \ - X(pa_stream_write) \ - X(pa_strerror) \ - X(pa_threaded_mainloop_free) \ - X(pa_threaded_mainloop_get_api) \ - X(pa_threaded_mainloop_lock) \ - X(pa_threaded_mainloop_new) \ - X(pa_threaded_mainloop_signal) \ - X(pa_threaded_mainloop_start) \ - X(pa_threaded_mainloop_stop) \ - X(pa_threaded_mainloop_unlock) \ - X(pa_threaded_mainloop_wait) - -#define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME PULSE_AUDIO_SYMBOLS_CLASS_NAME -#define LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST PULSE_AUDIO_SYMBOLS_LIST -#include "webrtc/base/latebindingsymboltable.h.def" - -} // namespace cricket - -#endif // TALK_SOUND_PULSEAUDIOSYMBOLTABLE_H_ diff --git a/sound/sounddevicelocator.h b/sound/sounddevicelocator.h deleted file mode 100644 index 420226f..0000000 --- a/sound/sounddevicelocator.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDDEVICELOCATOR_H_ -#define TALK_SOUND_SOUNDDEVICELOCATOR_H_ - -#include <string> - -#include "webrtc/base/constructormagic.h" - -namespace cricket { - -// A simple container for holding the name of a device and any additional id -// information needed to locate and open it. Implementations of -// SoundSystemInterface must subclass this to add any id information that they -// need. -class SoundDeviceLocator { - public: - virtual ~SoundDeviceLocator() {} - - // Human-readable name for the device. - const std::string &name() const { return name_; } - - // Name sound system uses to locate this device. - const std::string &device_name() const { return device_name_; } - - // Makes a duplicate of this locator. - virtual SoundDeviceLocator *Copy() const = 0; - - protected: - SoundDeviceLocator(const std::string &name, - const std::string &device_name) - : name_(name), device_name_(device_name) {} - - explicit SoundDeviceLocator(const SoundDeviceLocator &that) - : name_(that.name_), device_name_(that.device_name_) {} - - std::string name_; - std::string device_name_; - - private: - DISALLOW_ASSIGN(SoundDeviceLocator); -}; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDDEVICELOCATOR_H_ diff --git a/sound/soundinputstreaminterface.h b/sound/soundinputstreaminterface.h deleted file mode 100644 index e557392..0000000 --- a/sound/soundinputstreaminterface.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDINPUTSTREAMINTERFACE_H_ -#define TALK_SOUND_SOUNDINPUTSTREAMINTERFACE_H_ - -#include "webrtc/base/constructormagic.h" -#include "webrtc/base/sigslot.h" - -namespace cricket { - -// Interface for consuming an input stream from a recording device. -// Semantics and thread-safety of StartReading()/StopReading() are the same as -// for rtc::Worker. -class SoundInputStreamInterface { - public: - virtual ~SoundInputStreamInterface() {} - - // Starts the reading of samples on the current thread. - virtual bool StartReading() = 0; - // Stops the reading of samples. - virtual bool StopReading() = 0; - - // Retrieves the current input volume for this stream. Nominal range is - // defined by SoundSystemInterface::k(Max|Min)Volume, but values exceeding the - // max may be possible in some implementations. This call retrieves the actual - // volume currently in use by the OS, not a cached value from a previous - // (Get|Set)Volume() call. - virtual bool GetVolume(int *volume) = 0; - - // Changes the input volume for this stream. Nominal range is defined by - // SoundSystemInterface::k(Max|Min)Volume. The effect of exceeding kMaxVolume - // is implementation-defined. - virtual bool SetVolume(int volume) = 0; - - // Closes this stream object. If currently reading then this may only be - // called from the reading thread. - virtual bool Close() = 0; - - // Get the latency of the stream. - virtual int LatencyUsecs() = 0; - - // Notifies the consumer of new data read from the device. - // The first parameter is a pointer to the data read, and is only valid for - // the duration of the call. - // The second parameter is the amount of data read in bytes (i.e., the valid - // length of the memory pointed to). - // The 3rd parameter is the stream that is issuing the callback. - sigslot::signal3<const void *, size_t, - SoundInputStreamInterface *> SignalSamplesRead; - - protected: - SoundInputStreamInterface() {} - - private: - DISALLOW_COPY_AND_ASSIGN(SoundInputStreamInterface); -}; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_ diff --git a/sound/soundoutputstreaminterface.h b/sound/soundoutputstreaminterface.h deleted file mode 100644 index 294906d..0000000 --- a/sound/soundoutputstreaminterface.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_ -#define TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_ - -#include "webrtc/base/constructormagic.h" -#include "webrtc/base/sigslot.h" - -namespace cricket { - -// Interface for outputting a stream to a playback device. -// Semantics and thread-safety of EnableBufferMonitoring()/ -// DisableBufferMonitoring() are the same as for rtc::Worker. -class SoundOutputStreamInterface { - public: - virtual ~SoundOutputStreamInterface() {} - - // Enables monitoring the available buffer space on the current thread. - virtual bool EnableBufferMonitoring() = 0; - // Disables the monitoring. - virtual bool DisableBufferMonitoring() = 0; - - // Write the given samples to the devices. If currently monitoring then this - // may only be called from the monitoring thread. - virtual bool WriteSamples(const void *sample_data, - size_t size) = 0; - - // Retrieves the current output volume for this stream. Nominal range is - // defined by SoundSystemInterface::k(Max|Min)Volume, but values exceeding the - // max may be possible in some implementations. This call retrieves the actual - // volume currently in use by the OS, not a cached value from a previous - // (Get|Set)Volume() call. - virtual bool GetVolume(int *volume) = 0; - - // Changes the output volume for this stream. Nominal range is defined by - // SoundSystemInterface::k(Max|Min)Volume. The effect of exceeding kMaxVolume - // is implementation-defined. - virtual bool SetVolume(int volume) = 0; - - // Closes this stream object. If currently monitoring then this may only be - // called from the monitoring thread. - virtual bool Close() = 0; - - // Get the latency of the stream. - virtual int LatencyUsecs() = 0; - - // Notifies the producer of the available buffer space for writes. - // It fires continuously as long as the space is greater than zero. - // The first parameter is the amount of buffer space available for data to - // be written (i.e., the maximum amount of data that can be written right now - // with WriteSamples() without blocking). - // The 2nd parameter is the stream that is issuing the callback. - sigslot::signal2<size_t, SoundOutputStreamInterface *> SignalBufferSpace; - - protected: - SoundOutputStreamInterface() {} - - private: - DISALLOW_COPY_AND_ASSIGN(SoundOutputStreamInterface); -}; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDOUTPUTSTREAMINTERFACE_H_ diff --git a/sound/soundsystemfactory.h b/sound/soundsystemfactory.h deleted file mode 100644 index 06a1c3f..0000000 --- a/sound/soundsystemfactory.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDSYSTEMFACTORY_H_ -#define TALK_SOUND_SOUNDSYSTEMFACTORY_H_ - -#include "webrtc/base/referencecountedsingletonfactory.h" - -namespace cricket { - -class SoundSystemInterface; - -typedef rtc::ReferenceCountedSingletonFactory<SoundSystemInterface> - SoundSystemFactory; - -typedef rtc::rcsf_ptr<SoundSystemInterface> SoundSystemHandle; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDSYSTEMFACTORY_H_ diff --git a/sound/soundsysteminterface.cc b/sound/soundsysteminterface.cc deleted file mode 100644 index b432262..0000000 --- a/sound/soundsysteminterface.cc +++ /dev/null @@ -1,46 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/soundsysteminterface.h" - -#include "talk/sound/sounddevicelocator.h" - -namespace cricket { - -void SoundSystemInterface::ClearSoundDeviceLocatorList( - SoundSystemInterface::SoundDeviceLocatorList *devices) { - for (SoundDeviceLocatorList::iterator i = devices->begin(); - i != devices->end(); - ++i) { - if (*i) { - delete *i; - } - } - devices->clear(); -} - -} // namespace cricket diff --git a/sound/soundsysteminterface.h b/sound/soundsysteminterface.h deleted file mode 100644 index 5d3e84b..0000000 --- a/sound/soundsysteminterface.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDSYSTEMINTERFACE_H_ -#define TALK_SOUND_SOUNDSYSTEMINTERFACE_H_ - -#include <vector> - -#include "webrtc/base/constructormagic.h" - -namespace cricket { - -class SoundDeviceLocator; -class SoundInputStreamInterface; -class SoundOutputStreamInterface; - -// Interface for a platform's sound system. -// Implementations must guarantee thread-safety for at least the following use -// cases: -// 1) Concurrent enumeration and opening of devices from different threads. -// 2) Concurrent use of different Sound(Input|Output)StreamInterface -// instances from different threads (but concurrent use of the _same_ one from -// different threads need not be supported). -class SoundSystemInterface { - public: - typedef std::vector<SoundDeviceLocator *> SoundDeviceLocatorList; - - enum SampleFormat { - // Only one supported sample format at this time. - // The values here may be used in lookup tables, so they shouldn't change. - FORMAT_S16LE = 0, - }; - - enum Flags { - // Enable reporting the current stream latency in - // Sound(Input|Output)StreamInterface. See those classes for more details. - FLAG_REPORT_LATENCY = (1 << 0), - }; - - struct OpenParams { - // Format for the sound stream. - SampleFormat format; - // Sampling frequency in hertz. - unsigned int freq; - // Number of channels in the PCM stream. - unsigned int channels; - // Misc flags. Should be taken from the Flags enum above. - int flags; - // Desired latency, measured as number of bytes of sample data - int latency; - }; - - // Special values for the "latency" field of OpenParams. - // Use this one to say you don't care what the latency is. The sound system - // will optimize for other things instead. - static const int kNoLatencyRequirements = -1; - // Use this one to say that you want the sound system to pick an appropriate - // small latency value. The sound system may pick the minimum allowed one, or - // a slightly higher one in the event that the true minimum requires an - // undesirable trade-off. - static const int kLowLatency = 0; - - // Max value for the volume parameters for Sound(Input|Output)StreamInterface. - static const int kMaxVolume = 255; - // Min value for the volume parameters for Sound(Input|Output)StreamInterface. - static const int kMinVolume = 0; - - // Helper for clearing a locator list and deleting the entries. - static void ClearSoundDeviceLocatorList(SoundDeviceLocatorList *devices); - - virtual ~SoundSystemInterface() {} - - virtual bool Init() = 0; - virtual void Terminate() = 0; - - // Enumerates the available devices. (Any pre-existing locators in the lists - // are deleted.) - virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices) = 0; - virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices) = 0; - - // Gets a special locator for the default device. - virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device) = 0; - virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device) = 0; - - // Opens the given device, or returns NULL on error. - virtual SoundOutputStreamInterface *OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) = 0; - virtual SoundInputStreamInterface *OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) = 0; - - // A human-readable name for this sound system. - virtual const char *GetName() const = 0; - - protected: - SoundSystemInterface() {} - - private: - DISALLOW_COPY_AND_ASSIGN(SoundSystemInterface); -}; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDSYSTEMINTERFACE_H_ diff --git a/sound/soundsystemproxy.cc b/sound/soundsystemproxy.cc deleted file mode 100644 index 737a6bb..0000000 --- a/sound/soundsystemproxy.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/sound/soundsystemproxy.h" - -namespace cricket { - -bool SoundSystemProxy::EnumeratePlaybackDevices( - SoundDeviceLocatorList *devices) { - return wrapped_ ? wrapped_->EnumeratePlaybackDevices(devices) : false; -} - -bool SoundSystemProxy::EnumerateCaptureDevices( - SoundDeviceLocatorList *devices) { - return wrapped_ ? wrapped_->EnumerateCaptureDevices(devices) : false; -} - -bool SoundSystemProxy::GetDefaultPlaybackDevice( - SoundDeviceLocator **device) { - return wrapped_ ? wrapped_->GetDefaultPlaybackDevice(device) : false; -} - -bool SoundSystemProxy::GetDefaultCaptureDevice( - SoundDeviceLocator **device) { - return wrapped_ ? wrapped_->GetDefaultCaptureDevice(device) : false; -} - -SoundOutputStreamInterface *SoundSystemProxy::OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return wrapped_ ? wrapped_->OpenPlaybackDevice(device, params) : NULL; -} - -SoundInputStreamInterface *SoundSystemProxy::OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms) { - return wrapped_ ? wrapped_->OpenCaptureDevice(device, params) : NULL; -} - -} // namespace cricket diff --git a/sound/soundsystemproxy.h b/sound/soundsystemproxy.h deleted file mode 100644 index 0b8a3da..0000000 --- a/sound/soundsystemproxy.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * libjingle - * Copyright 2004--2010, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_SOUND_SOUNDSYSTEMPROXY_H_ -#define TALK_SOUND_SOUNDSYSTEMPROXY_H_ - -#include "talk/sound/soundsysteminterface.h" -#include "webrtc/base/basictypes.h" // for NULL - -namespace cricket { - -// A SoundSystemProxy is a sound system that defers to another one. -// Init(), Terminate(), and GetName() are left as pure virtual, so a sub-class -// must define them. -class SoundSystemProxy : public SoundSystemInterface { - public: - SoundSystemProxy() : wrapped_(NULL) {} - - // Each of these methods simply defers to wrapped_ if non-NULL, else fails. - - virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices); - virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices); - - virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device); - virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device); - - virtual SoundOutputStreamInterface *OpenPlaybackDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - virtual SoundInputStreamInterface *OpenCaptureDevice( - const SoundDeviceLocator *device, - const OpenParams ¶ms); - - protected: - SoundSystemInterface *wrapped_; -}; - -} // namespace cricket - -#endif // TALK_SOUND_SOUNDSYSTEMPROXY_H_ diff --git a/xmpp/asyncsocket.h b/xmpp/asyncsocket.h index 5e83b63..2514742 100644 --- a/xmpp/asyncsocket.h +++ b/xmpp/asyncsocket.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _ASYNCSOCKET_H_ -#define _ASYNCSOCKET_H_ +#ifndef TALK_XMPP_ASYNCSOCKET_H_ +#define TALK_XMPP_ASYNCSOCKET_H_ #include <string> @@ -86,4 +86,4 @@ public: } -#endif +#endif // TALK_XMPP_ASYNCSOCKET_H_ diff --git a/xmpp/chatroommodule.h b/xmpp/chatroommodule.h index 47a7106..8358fc1 100644 --- a/xmpp/chatroommodule.h +++ b/xmpp/chatroommodule.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _multiuserchatmodule_h_ -#define _multiuserchatmodule_h_ +#ifndef TALK_XMPP_CHATROOMMODULE_H_ +#define TALK_XMPP_CHATROOMMODULE_H_ #include "talk/xmpp/module.h" #include "talk/xmpp/rostermodule.h" @@ -267,4 +267,4 @@ public: } -#endif +#endif // TALK_XMPP_CHATROOMMODULE_H_ diff --git a/xmpp/constants.cc b/xmpp/constants.cc index a2179ca..297eafd 100644 --- a/xmpp/constants.cc +++ b/xmpp/constants.cc @@ -29,9 +29,9 @@ #include <string> -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlconstants.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/jid.h" #include "webrtc/base/basicdefs.h" diff --git a/xmpp/constants.h b/xmpp/constants.h index 6d94095..6aa1a54 100644 --- a/xmpp/constants.h +++ b/xmpp/constants.h @@ -29,7 +29,7 @@ #define TALK_XMPP_CONSTANTS_H_ #include <string> -#include "talk/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/qname.h" #include "talk/xmpp/jid.h" namespace buzz { diff --git a/xmpp/hangoutpubsubclient.cc b/xmpp/hangoutpubsubclient.cc index 834f844..63f5bcf 100644 --- a/xmpp/hangoutpubsubclient.cc +++ b/xmpp/hangoutpubsubclient.cc @@ -27,8 +27,8 @@ #include "talk/xmpp/hangoutpubsubclient.h" -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/jid.h" #include "webrtc/base/logging.h" diff --git a/xmpp/hangoutpubsubclient_unittest.cc b/xmpp/hangoutpubsubclient_unittest.cc index c12b791..555ee5c 100644 --- a/xmpp/hangoutpubsubclient_unittest.cc +++ b/xmpp/hangoutpubsubclient_unittest.cc @@ -3,8 +3,8 @@ #include <string> -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/hangoutpubsubclient.h" @@ -29,7 +29,7 @@ #define TALK_XMPP_JID_H_ #include <string> -#include "talk/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" #include "webrtc/base/basictypes.h" namespace buzz { diff --git a/xmpp/jingleinfotask.h b/xmpp/jingleinfotask.h index 5865a77..18cb5a9 100644 --- a/xmpp/jingleinfotask.h +++ b/xmpp/jingleinfotask.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_ -#define TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_ +#ifndef TALK_XMPP_JINGLEINFOTASK_H_ +#define TALK_XMPP_JINGLEINFOTASK_H_ #include <vector> @@ -58,4 +58,4 @@ class JingleInfoTask : public XmppTask { }; } -#endif // TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_ +#endif // TALK_XMPP_JINGLEINFOTASK_H_ diff --git a/xmpp/module.h b/xmpp/module.h index 37396ef..a5d0687 100644 --- a/xmpp/module.h +++ b/xmpp/module.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _module_h_ -#define _module_h_ +#ifndef TALK_XMPP_MODULE_H_ +#define TALK_XMPP_MODULE_H_ #include "talk/xmpp/xmppengine.h" @@ -49,4 +49,4 @@ public: }; } -#endif +#endif // TALK_XMPP_MODULE_H_ diff --git a/xmpp/moduleimpl.h b/xmpp/moduleimpl.h index 32a182b..897bfce 100644 --- a/xmpp/moduleimpl.h +++ b/xmpp/moduleimpl.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _moduleimpl_h_ -#define _moduleimpl_h_ +#ifndef TALK_XMPP_MODULEIMPL_H_ +#define TALK_XMPP_MODULEIMPL_H_ #include "talk/xmpp/module.h" #include "talk/xmpp/xmppengine.h" @@ -90,4 +90,4 @@ private: } -#endif +#endif // TALK_XMPP_MODULEIMPL_H_ diff --git a/xmpp/mucroomconfigtask_unittest.cc b/xmpp/mucroomconfigtask_unittest.cc index 00a9c6e..bf5e7b4 100644 --- a/xmpp/mucroomconfigtask_unittest.cc +++ b/xmpp/mucroomconfigtask_unittest.cc @@ -28,7 +28,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/mucroomconfigtask.h" diff --git a/xmpp/mucroomdiscoverytask_unittest.cc b/xmpp/mucroomdiscoverytask_unittest.cc index ef7e477..e1a633e 100644 --- a/xmpp/mucroomdiscoverytask_unittest.cc +++ b/xmpp/mucroomdiscoverytask_unittest.cc @@ -28,7 +28,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/mucroomdiscoverytask.h" diff --git a/xmpp/mucroomlookuptask_unittest.cc b/xmpp/mucroomlookuptask_unittest.cc index 341fd3f..03be292 100644 --- a/xmpp/mucroomlookuptask_unittest.cc +++ b/xmpp/mucroomlookuptask_unittest.cc @@ -28,7 +28,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/mucroomlookuptask.h" diff --git a/xmpp/mucroomuniquehangoutidtask_unittest.cc b/xmpp/mucroomuniquehangoutidtask_unittest.cc index 5cabcbe..42bed13 100644 --- a/xmpp/mucroomuniquehangoutidtask_unittest.cc +++ b/xmpp/mucroomuniquehangoutidtask_unittest.cc @@ -28,7 +28,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/mucroomuniquehangoutidtask.h" diff --git a/xmpp/pingtask_unittest.cc b/xmpp/pingtask_unittest.cc index ce595a8..fe88a5c 100644 --- a/xmpp/pingtask_unittest.cc +++ b/xmpp/pingtask_unittest.cc @@ -28,7 +28,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/pingtask.h" diff --git a/xmpp/plainsaslhandler.h b/xmpp/plainsaslhandler.h index 2ca364c..31032e4 100644 --- a/xmpp/plainsaslhandler.h +++ b/xmpp/plainsaslhandler.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _PLAINSASLHANDLER_H_ -#define _PLAINSASLHANDLER_H_ +#ifndef TALK_XMPP_PLAINSASLHANDLER_H_ +#define TALK_XMPP_PLAINSASLHANDLER_H_ #include <algorithm> #include "talk/xmpp/saslhandler.h" @@ -78,4 +78,4 @@ private: } -#endif +#endif // TALK_XMPP_PLAINSASLHANDLER_H_ diff --git a/xmpp/presenceouttask.h b/xmpp/presenceouttask.h index a72457e..53bbae5 100644 --- a/xmpp/presenceouttask.h +++ b/xmpp/presenceouttask.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _PRESENCEOUTTASK_H_ -#define _PRESENCEOUTTASK_H_ +#ifndef TALK_XMPP_PRESENCEOUTTASK_H_ +#define TALK_XMPP_PRESENCEOUTTASK_H_ #include "talk/xmpp/presencestatus.h" #include "talk/xmpp/xmppengine.h" @@ -51,4 +51,4 @@ private: } -#endif +#endif // TALK_XMPP_PRESENCEOUTTASK_H_ diff --git a/xmpp/pubsub_task.h b/xmpp/pubsub_task.h index 45a7462..2787cbc 100644 --- a/xmpp/pubsub_task.h +++ b/xmpp/pubsub_task.h @@ -30,7 +30,7 @@ #include <map> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/jid.h" #include "talk/xmpp/xmpptask.h" diff --git a/xmpp/pubsubclient_unittest.cc b/xmpp/pubsubclient_unittest.cc index 135dc37..f191a18 100644 --- a/xmpp/pubsubclient_unittest.cc +++ b/xmpp/pubsubclient_unittest.cc @@ -3,8 +3,8 @@ #include <string> -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/jid.h" diff --git a/xmpp/pubsubstateclient.h b/xmpp/pubsubstateclient.h index 2acbdaa..09ef0f4 100644 --- a/xmpp/pubsubstateclient.h +++ b/xmpp/pubsubstateclient.h @@ -32,8 +32,8 @@ #include <string> #include <vector> -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/jid.h" #include "talk/xmpp/pubsubclient.h" diff --git a/xmpp/pubsubtasks_unittest.cc b/xmpp/pubsubtasks_unittest.cc index 98cb7c1..48cd04a 100644 --- a/xmpp/pubsubtasks_unittest.cc +++ b/xmpp/pubsubtasks_unittest.cc @@ -3,8 +3,8 @@ #include <string> -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/fakexmppclient.h" #include "talk/xmpp/iqtask.h" diff --git a/xmpp/rostermodule.h b/xmpp/rostermodule.h index 7e14dc1..dfb647d 100644 --- a/xmpp/rostermodule.h +++ b/xmpp/rostermodule.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _rostermodule_h_ -#define _rostermodule_h_ +#ifndef TALK_XMPP_ROSTERMODULE_H_ +#define TALK_XMPP_ROSTERMODULE_H_ #include "talk/xmpp/module.h" @@ -345,4 +345,4 @@ public: } -#endif +#endif // TALK_XMPP_ROSTERMODULE_H_ diff --git a/xmpp/rostermodule_unittest.cc b/xmpp/rostermodule_unittest.cc index 0e2b268..cb7f773 100644 --- a/xmpp/rostermodule_unittest.cc +++ b/xmpp/rostermodule_unittest.cc @@ -29,7 +29,7 @@ #include <sstream> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/rostermodule.h" #include "talk/xmpp/util_unittest.h" diff --git a/xmpp/rostermoduleimpl.h b/xmpp/rostermoduleimpl.h index a6b15cf..37d1117 100644 --- a/xmpp/rostermoduleimpl.h +++ b/xmpp/rostermoduleimpl.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _rostermoduleimpl_h_ -#define _rostermoduleimpl_h_ +#ifndef TALK_XMPP_XMPPTHREAD_H_ +#define TALK_XMPP_XMPPTHREAD_H_ #include "talk/xmpp/moduleimpl.h" #include "talk/xmpp/rostermodule.h" @@ -299,4 +299,4 @@ private: } -#endif +#endif // TALK_XMPP_XMPPTHREAD_H_ diff --git a/xmpp/saslcookiemechanism.h b/xmpp/saslcookiemechanism.h index 7a19d3e..eda142c 100644 --- a/xmpp/saslcookiemechanism.h +++ b/xmpp/saslcookiemechanism.h @@ -28,8 +28,8 @@ #ifndef TALK_XMPP_SASLCOOKIEMECHANISM_H_ #define TALK_XMPP_SASLCOOKIEMECHANISM_H_ -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/saslmechanism.h" diff --git a/xmpp/saslhandler.h b/xmpp/saslhandler.h index bead8aa..a4a73e4 100644 --- a/xmpp/saslhandler.h +++ b/xmpp/saslhandler.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _SASLHANDLER_H_ -#define _SASLHANDLER_H_ +#ifndef TALK_XMPP_SASLHANDLER_H_ +#define TALK_XMPP_SASLHANDLER_H_ #include <string> #include <vector> @@ -56,4 +56,4 @@ public: } -#endif +#endif // TALK_XMPP_SASLHANDLER_H_ diff --git a/xmpp/saslmechanism.cc b/xmpp/saslmechanism.cc index 488007c..8877084 100644 --- a/xmpp/saslmechanism.cc +++ b/xmpp/saslmechanism.cc @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/saslmechanism.h" #include "webrtc/base/base64.h" diff --git a/xmpp/saslmechanism.h b/xmpp/saslmechanism.h index f2e5adc..ee419d1 100644 --- a/xmpp/saslmechanism.h +++ b/xmpp/saslmechanism.h @@ -25,8 +25,8 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _SASLMECHANISM_H_ -#define _SASLMECHANISM_H_ +#ifndef TALK_XMPP_SASLMECHANISM_H_ +#define TALK_XMPP_SASLMECHANISM_H_ #include <string> @@ -71,4 +71,4 @@ protected: } -#endif +#endif // TALK_XMPP_SASLMECHANISM_H_ diff --git a/xmpp/util_unittest.cc b/xmpp/util_unittest.cc index 66fb5ef..3e47d3f 100644 --- a/xmpp/util_unittest.cc +++ b/xmpp/util_unittest.cc @@ -4,7 +4,7 @@ #include <iostream> #include <sstream> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/util_unittest.h" #include "talk/xmpp/xmppengine.h" #include "webrtc/base/gunit.h" diff --git a/xmpp/xmppengine.h b/xmpp/xmppengine.h index 68f10fe..461e90f 100644 --- a/xmpp/xmppengine.h +++ b/xmpp/xmppengine.h @@ -25,12 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _xmppengine_h_ -#define _xmppengine_h_ +#ifndef TALK_XMPP_XMPPENGINE_H_ +#define TALK_XMPP_XMPPENGINE_H_ // also part of the API -#include "talk/xmllite/qname.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/jid.h" @@ -346,4 +346,4 @@ public: } while (false) \ -#endif +#endif // TALK_XMPP_XMPPENGINE_H_ diff --git a/xmpp/xmppengine_unittest.cc b/xmpp/xmppengine_unittest.cc index 8d6c858..b519a65 100644 --- a/xmpp/xmppengine_unittest.cc +++ b/xmpp/xmppengine_unittest.cc @@ -4,7 +4,7 @@ #include <iostream> #include <sstream> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/plainsaslhandler.h" #include "talk/xmpp/saslplainmechanism.h" diff --git a/xmpp/xmppengineimpl.cc b/xmpp/xmppengineimpl.cc index bba96a4..5de9de7 100644 --- a/xmpp/xmppengineimpl.cc +++ b/xmpp/xmppengineimpl.cc @@ -31,8 +31,8 @@ #include <sstream> #include <vector> -#include "talk/xmllite/xmlelement.h" -#include "talk/xmllite/xmlprinter.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/saslhandler.h" #include "talk/xmpp/xmpplogintask.h" diff --git a/xmpp/xmpplogintask.cc b/xmpp/xmpplogintask.cc index 2183c40..a48a94c 100644 --- a/xmpp/xmpplogintask.cc +++ b/xmpp/xmpplogintask.cc @@ -30,7 +30,7 @@ #include <string> #include <vector> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/jid.h" #include "talk/xmpp/saslmechanism.h" diff --git a/xmpp/xmpplogintask_unittest.cc b/xmpp/xmpplogintask_unittest.cc index 1cc16cc..ae9a554 100644 --- a/xmpp/xmpplogintask_unittest.cc +++ b/xmpp/xmpplogintask_unittest.cc @@ -4,7 +4,7 @@ #include <iostream> #include <sstream> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/plainsaslhandler.h" #include "talk/xmpp/saslplainmechanism.h" diff --git a/xmpp/xmppstanzaparser.cc b/xmpp/xmppstanzaparser.cc index 3e6ad47..4795839 100644 --- a/xmpp/xmppstanzaparser.cc +++ b/xmpp/xmppstanzaparser.cc @@ -27,7 +27,7 @@ #include "talk/xmpp/xmppstanzaparser.h" -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/constants.h" #include "webrtc/base/common.h" #ifdef EXPAT_RELATIVE_PATH diff --git a/xmpp/xmppstanzaparser.h b/xmpp/xmppstanzaparser.h index d9a8933..ffb3275 100644 --- a/xmpp/xmppstanzaparser.h +++ b/xmpp/xmppstanzaparser.h @@ -25,11 +25,11 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _xmppstanzaparser_h_ -#define _xmppstanzaparser_h_ +#ifndef TALK_XMPP_XMPPSTANZAPARSER_H_ +#define TALK_XMPP_XMPPSTANZAPARSER_H_ -#include "talk/xmllite/xmlbuilder.h" -#include "talk/xmllite/xmlparser.h" +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" namespace buzz { @@ -94,4 +94,4 @@ private: } -#endif +#endif // TALK_XMPP_XMPPSTANZAPARSER_H_ diff --git a/xmpp/xmppstanzaparser_unittest.cc b/xmpp/xmppstanzaparser_unittest.cc index 4e60d81..0b114c0 100644 --- a/xmpp/xmppstanzaparser_unittest.cc +++ b/xmpp/xmppstanzaparser_unittest.cc @@ -4,7 +4,7 @@ #include <iostream> #include <sstream> #include <string> -#include "talk/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" #include "talk/xmpp/xmppstanzaparser.h" #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" |