android WebRtc 无法推流以及拉流有视频无声音问题

最近在开发使用WebRtc进行视频通话和语音通话,我使用的设备是MTK的手机,期间后台的技术人员几乎没法提供任何帮助,只有接口和测试的web端,有遇到不能推流。推流成功网页端有画面有声音,但是安卓端有画面,没有声音的情况

问题一:无法推流

一开始遇到问题是在进行推流前进行sdp的交换,一直返回返回code:400 的情况,我们知道在 onCreateSuccess 方法回调中拿到offerSdp,用于向SRS服务进行网络请求,这时候一定注意其间的网络请求地址,token的拼接,peerConnection.addTransceiver添加的视频轨道和音频轨道一定要按照实际需求来,没有的就不要全部添加;全部中间步骤没错,这时候还是返回code:400的问题,后来在后台看到的错误日志是**create session : create session : add publisher : publish negotiate : no found valid H.264 payload type,**然后就是网上搜索下同样的问题,这里感谢

冬季穿短裤同学的知识帮助,就是接口请求里offer sdpm=videoH.264 相关信息,即WebRTC在createOffer 时,返回的sdp 没有H.264 相关信息;Android的使用WebRTC仅支持硬件上 H.264 解码和编码,并且仅支持部分芯片组。因此,如果设备不支持硬件 H.264 或具有不受支持的芯片组,您将只能使用 VP8、VP9。支持的芯片组仅有OMX.qcom.*OMX.Exynos.*,不支持的要自行添加。

解决方法:

VideoEncoderFactory创建

在创建PeerConnectionFactory ,可以设VideoEncoderFactory

val encoderFactory = DefaultVideoEncoderFactory(eglBaseContext, true, true)
val peerConnectionFactory = PeerConnectionFactory.builder()
            .setVideoEncoderFactory(encoderFactory)
            .createPeerConnectionFactory()

public class DefaultVideoEncoderFactory implements VideoEncoderFactory {
	/**
     * 硬解件编码工厂
     */
    private final VideoEncoderFactory hardwareVideoEncoderFactory;
    /**
     * 软件编码工厂
     */
    private final VideoEncoderFactory softwareVideoEncoderFactory = new SoftwareVideoEncoderFactory();

    public DefaultVideoEncoderFactory(Context eglContext, boolean enableIntelVp8Encoder, boolean enableH264HighProfile) {
    	//创建硬解编码工厂
        this.hardwareVideoEncoderFactory = new HardwareVideoEncoderFactory(eglContext, enableIntelVp8Encoder, enableH264HighProfile);
    }
	/**
     * 注意这个构造方法仅包可见
     */
    DefaultVideoEncoderFactory(VideoEncoderFactory hardwareVideoEncoderFactory) {
        this.hardwareVideoEncoderFactory = hardwareVideoEncoderFactory;
    }
    ...
}

/**
 * 用于创建视频编码器工厂
 */
public interface VideoEncoderFactory {
	/** 
	 * 为给定的视频编解码器创建一个编码器。
	 */
    @Nullable
    @CalledByNative
    VideoEncoder createEncoder(VideoCodecInfo var1);
	/**
     * 枚举支持的视频编解码器列表。这个方法只会被调用一次,结果将被缓存。
     */
    @CalledByNative
    VideoCodecInfo[] getSupportedCodecs();

    @CalledByNative
    default VideoCodecInfo[] getImplementations() {
        return this.getSupportedCodecs();
    }

    @CalledByNative
    default VideoEncoderFactory.VideoEncoderSelector getEncoderSelector() {
        return null;
    }
}

关键在于getSupportedCodecs()HardwareVideoEncoderFactory中是如何实现的

@Override
public VideoCodecInfo[] getSupportedCodecs() {
	// Android19以下不支持硬解编码.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        return new VideoCodecInfo[0];
    }

    List<VideoCodecInfo> supportedCodecInfos = new ArrayList<>();
    // 按优先顺序生成支持的编解码器列表:
    // VP8, VP9, H264 (high profile), and H264 (baseline profile).
    for (VideoCodecType type : new VideoCodecType[]{VideoCodecType.VP8, VideoCodecType.VP9, VideoCodecType.H264}) {
		//查找编解码器类型,这里是关键
		MediaCodecInfo codec = findCodecForType(type);
		if (codec != null) {
			String name = type.name();
            // supported by the decoder.
            if (type == VideoCodecType.H264 && isH264HighProfileSupported(codec)) {
                supportedCodecInfos.add(new VideoCodecInfo(
                            name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)));
            }
			supportedCodecInfos.add(new VideoCodecInfo(
                        name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)));
		}
    }
	return supportedCodecInfos.toArray(new VideoCodecInfo[0]);
}

findCodecForType(VideoCodecType),根据类型查找支持的编解码器

private MediaCodecInfo findCodecForType(VideoCodecType type) {
    for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
        MediaCodecInfo info = null;
        try {
            info = MediaCodecList.getCodecInfoAt(i);
        } catch (IllegalArgumentException e) {
        	//无法检索编码器编解码器信息
            Logging.e(TAG, "Cannot retrieve encoder codec info", e);
        }
        //编解器信息为null,或者不是编解码器不是编码器
		if (info == null || !info.isEncoder()) {
            continue;
        }
        //判断编解码器是否支持,这里就会去判断不同的芯片组是否支持
		if (isSupportedCodec(info, type)) {
			return info;
		}
	}
    return null; // 不支持的类型
}

isSupportedCodec(MediaCodecInfo, VideoCodecType):判断MediaCodecInfo和VideoCodecType结合设备芯片组信息是否支持

private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecType type) {
	if (!MediaCodecUtils.codecSupportsType(info, type)) {
		return false;
	}
	// Check for a supported color format.
	if (MediaCodecUtils.selectColorFormat(
                MediaCodecUtils.ENCODER_COLOR_FORMATS, /*这一步其实就可以判断编解码器是否支持了给定的类型了,如果不抛异常的话*/info.getCapabilitiesForType(type.mimeType()))
                == null) {
		return false;
	}
	return isHardwareSupportedInCurrentSdk(info, type) && isMediaCodecAllowed(info);
}

/**
 * 结合当前的sdk,再次判断是否支持
 */
private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecType type) {
	switch (type) {
		case VP8:
			return isHardwareSupportedInCurrentSdkVp8(info);
        case VP9:
			return isHardwareSupportedInCurrentSdkVp9(info);
		case H264:
			return isHardwareSupportedInCurrentSdkH264(info);
	}
	return false;
}

private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) {
	//H264 硬件在此类型设备上可能表现不佳。"SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"
	if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) {
		return false;
	} else {
		String name = info.getName();
		//问题就在这,写死的仅支持的硬件编码器解码器组件名称的前缀。
		//所以要在后面自行追加我们自己设备支持H264名称信息。
		return name.startsWith("OMX.qcom.") && VERSION.SDK_INT >= 19 || name.startsWith("OMX.Exynos.") && VERSION.SDK_INT >= 21;
	}
}
------------------------------------------------

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/csdn_shen0221/article/details/119982257

Github传送门

问题二:拉流成功出现没有声音

因为没有声音只有在mtk的设备上没有,在我其他的高通处理器手机是正常的,我们一开始想到的可能就是收到的流和编解码问题,还是比较相信webRtc的sdk没有问题的,事实证明打脸了

解决思路:

1、拦截拉流时的音频流,通过反射回调拿到PCM数据,对pcm进行播放

 audioDeviceModule = JavaAudioDeviceModule.builder(applicationContext)
        .setSamplesReadyCallback {
            //音频输入数据,麦克风数据,原始pcm数据,可以直接录制成pcm文件,再转成mp3
            val audioFormat = it.audioFormat
            val channelCount = it.channelCount
            val sampleRate = it.sampleRate
            //pcm格式数据
            val data = it.data
        }
        .setAudioTrackStateCallback(object : JavaAudioDeviceModule.AudioTrackStateCallback {
            override fun onWebRtcAudioTrackStart() {
                audioDeviceModule.setAudioTrackSamplesReadyCallback {
                    //音频输出数据,通话时对方数据,原始pcm数据,可以直接录制成pcm文件,再转成mp3
                    val audioFormat = it.audioFormat
                    val channelCount = it.channelCount
                    val sampleRate = it.sampleRate
                    //pcm格式数据
                    val data = it.data
                } 
}

            override fun onWebRtcAudioTrackStop() {

            }
        })
        .createAudioDeviceModule()

回调数据拿到了pcm的信息audioFormat:2 channelCount:1 sampleRate:48000

a. 直接使用AudioTrack播放 data,在高通手机可以,但是在mtk平台不行

**b.生成.pcm文件,**两设备都可以播放

2、替换播放器,使用编译openSL ES播放文件流,可以播放,但是集成到项目里就不行了,这时候可以想到,应该时webrtc内部做了处理,在拉流播放的时候会关闭其他所有的声道播放

**3、使用蓝牙进行播放,**mtk设备使用蓝牙进行语音通话有声音,这时候立马想到数据流声道的问题

解决方法: 查资料发现webrtc播放音频流是在WebRtcAudioTrack中实现的,点击进去看到也是使用AudioTrack 进行播放的,使用了AudioManager.STREAM_VOICE_CALL 声道,我们类搜索将所有的Call声道换成AudioManager.STREAM_MUSIC 声道,以及AudioAttributes.USAGE_VOICE_COMMUNICATION改成AudioAttributes .USAGE_MEDIA

打开通话测试,冒得问题了

一定不要遗漏了AudioAttributes.USAGE_VOICE_COMMUNICATION

相关推荐
电子科技圈4 小时前
XMOS将在CES 2025上展出多款由边缘AI驱动的创新音效、音频、识别和处理解决方案
人工智能·科技·嵌入式硬件·mcu·物联网·音视频·iot
java156550579706 小时前
前端 mp4 视频改成 m3u8 流模式
前端·音视频
yunmoon016 小时前
一个功能强大的视频翻译和本地化配音工具,支持影视级双语字幕/视频配音
音视频
Jack153027682796 小时前
D2761是为保护扬声器所设计的音频限幅器,其限幅值可通过外接电 阻来调节,适合在个人电脑、便携式音响等系统中作音频限幅用。
电脑·手机·笔记本电脑·音视频·音响
非ban必选6 小时前
JavaCV视频文件音视频分离
ffmpeg·音视频
m0_748246618 小时前
使用webrtc-streamer查看实时监控
webrtc
飞飞-躺着更舒服16 小时前
多媒体文件解复用(Demuxing)过程
计算机视觉·音视频
最 上 川16 小时前
学在西电录播课使用python下载,通过解析m3u8协议、多线程下载ts视频块以及ffmpeg合并
爬虫·python·ffmpeg·音视频
红米饭配南瓜汤19 小时前
Android显示系统(09)- SurfaceFlinger的使用
android·音视频·媒体
红米饭配南瓜汤20 小时前
WebRTC服务质量(01)- Qos概述
webrtc