Live Streaming from Mobile Device Camera
Press a button — viewers see you live in 2–3 seconds. Behind those seconds: camera capture, H.264/H.265 hardware encoding, container assembly, sending to media server, and CDN delivery. Each stage has its own API and ways to break.
Video and Audio Capture
iOS. AVCaptureSession — entry point. Configure quality preset (sessionPreset = .hd1280x720), add AVCaptureDeviceInput for camera and microphone, add AVCaptureVideoDataOutput and AVCaptureAudioDataOutput with delegates. captureOutput(_:didOutput:from:) delivers CMSampleBuffer — raw frames from camera in real time.
Orientation: AVCaptureConnection.videoOrientation must be updated on device rotation. Without it, viewers see video sideways if broadcast started in portrait.
Android. Camera2 API or CameraX (recommended). CameraX.bindToLifecycle() with Preview + VideoCapture use case. VideoCapture.output.prepareRecording() — native recording. For streaming you need raw stream: ImageAnalysis use case with setOutputImageFormat(OUTPUT_IMAGE_FORMAT_YUV_420_888), process frames manually via MediaCodec.
Hardware Encoding
Software FFmpeg on phone — overheating and drained battery in 20 minutes. Use hardware encoder.
iOS: VideoToolbox — VTCompressionSession. Create session with kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder: kCFBooleanTrue. Callback outputCallback receives CMSampleBuffer with encoded H.264/H.265.
Key parameters:
-
kVTCompressionPropertyKey_RealTime: kCFBooleanTrue— realtime mode -
kVTCompressionPropertyKey_ProfileLevel: kVTProfileLevel_H264_High_AutoLevel -
kVTCompressionPropertyKey_AverageBitRate: 2_000_000(2 Mbps for 720p) -
kVTCompressionPropertyKey_MaxKeyFrameInterval: 60(keyframe every 2 sec at 30 fps)
Android: MediaCodec. MediaFormat.createVideoFormat("video/avc", width, height). configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE). In dequeueOutputBuffer get encoded NAL units.
For audio: AudioRecord → MediaCodec with audio/mp4a-latm (AAC-LC). Sample rate 44100 Hz, bitrate 128 kbps.
Packing into RTMP or SRT
From CMSampleBuffer/MediaCodec output get H.264 NAL units and AAC frames. Need to pack into transport protocol.
Ready libraries save from manual implementation:
-
iOS:
HaishinKit(Swift) — RTMP, SRT, HLS. Native Swift without FFmpeg dependency.RTMPStream,SRTStream. -
Android:
rtmp-rtsp-stream-client-java(Pedro Vicente) — RTMP and RTSP push.RtmpCamera2with CameraX.SRTStreamfor SRT. -
Flutter:
rtmp_streaming(wrapper over native libraries) or native platform channel. -
Cross-platform with FFmpeg:
ffmpeg-kit-ios/ffmpeg-kit-android—FFmpegKit.executeAsync("-f avfoundation -i 0:0 -c:v libx264 -preset ultrafast -f flv rtmp://..."). Works but higher CPU than hardware encoder.
Adaptive Bitrate on Poor Connection
User enters metro — connection drops. Without adaptation: buffer overflows, stream dies.
Monitoring: RTMPStream.info.byteCount in HaishinKit, NetworkInfo callback in rtmp-rtsp-stream-client-java — track sending speed. If actual bandwidth falls below current bitrate:
- Reduce
videoBitrate(HaishinKit:stream.videoSettings.bitRate) - Lower framerate 30 to 15 fps
- Reduce resolution 720p to 480p
Don't do this too often — max every 5–10 seconds, else quality constantly jumps.
Preview and UI
Camera preview — AVCaptureVideoPreviewLayer (iOS) / PreviewView CameraX (Android). Overlay UI: "Live" indicator, viewer count, bitrate and signal level.
Switch front/rear camera without interrupting stream: iOS — AVCaptureSession.beginConfiguration() → remove old input → add new → commitConfiguration(). HaishinKit supports in one line: stream.captureSettings.isVideoMirrored.
Timeline
Stream from camera implementation (RTMP or SRT) on one platform with preview and basic UI — 3–5 days. Cross-platform with adaptive bitrate, camera switching, specific media server integration — 1–2 weeks.







