语音对讲的实现(码流客户端与服务器)

简诉

使用ffmpeg拉流和推流实现对音频流的获取和发送,使用directsound实现对音频的采集和播放功能,视频播放显示可以使用D3D实现。本文提供客户端和服务器该功能的核心代码,工程涉及的其他功能有进行删除。

核心功能
1.码流播放处理 (码流客户端)
int ClientPlayer::PlayStream()
{
    m_bStop = false;
    m_nVideoIndex = -1;
    m_nAudioIndex = -1;
    int i = 0, res = 0, speak_ret = 0, nValue = 0;
    char buf[64] = { 0 };
    m_bPlaying = false;
    AVStream* in_stream;
    AVStream* out_stream;
    AVCodecContext* AudioCodecCtx = NULL;
    char sUrl[64] = {0};
    char sSpeakUrl[64] = { 0 };

    _snprintf_s(sUrl, sizeof(sUrl), sizeof(sUrl) - 1, "%s", "rtsp://admin:123456@192.168.1.100:554/main");
    _snprintf_s(sSpeakUrl, sizeof(sSpeakUrl), sizeof(sSpeakUrl) - 1, "%s", "rtsp://admin:123456@192.168.1.100:554/speak");


    //初始化网络
    avformat_network_init();

    //设置超时时间/连接协议/缓存区
    AVDictionary* optionsDict = NULL;
    av_dict_set(&optionsDict, "rtsp_transport", "udp", 0);
    av_dict_set(&optionsDict, "stimeout", "5000000", 0);
    av_dict_set(&optionsDict, "buffer_size", "8192000", 0);
    av_dict_set(&optionsDict, "recv_buffer_size", "8192000", 0);

    //中断回调处理
    m_lastReadPacktTime = av_gettime();
    m_pAVFmtCxt = avformat_alloc_context();
    m_pAVFmtCxt->interrupt_callback.opaque = this;
    m_pAVFmtCxt->interrupt_callback.callback = [](void* ctx)
    {
        ClientPlayer* p_this = (ClientPlayer*)ctx;
        int timeout = 3;
        if (av_gettime() - p_this->m_lastReadPacktTime > timeout * 1000 * 1000)
        {
            return -1;
        }
        return 0;
    };

    //打开码流文件
    res = avformat_open_input(&m_pAVFmtCxt, sUrl, NULL, &optionsDict);
    if (res < 0)
    {
        printf("avformat_open_input fail: %d", res);
        return -1;
    }

    //流探测时间配置
    m_pAVFmtCxt->probesize = 100 * 1024;    
    m_pAVFmtCxt->max_analyze_duration = 1 * AV_TIME_BASE;

    res = avformat_find_stream_info(m_pAVFmtCxt, NULL);
    if (res < 0)
    {
        printf("error %x in avformat_find_stream_info\n", res);
        return -1;
    }

    //根据输出url创建音频输出流上下文参数 
    if (m_bSupportMike)
    {
        int rt = avformat_alloc_output_context2(&m_pOutAVFmtCxt, NULL, "rtsp", sSpeakUrl);
        if (!m_pOutAVFmtCxt)
        {
            m_bSupportMike = false;
            printf("Could not create output context,rt=%d\n",rt);
        }
    }

    //查找视音频流
    av_dump_format(m_pAVFmtCxt, 0, sUrl, 0);
    printf("m_pAVFmtCxt->nb_streams:%d \n", m_pAVFmtCxt->nb_streams);
    for (i = 0; i < m_pAVFmtCxt->nb_streams; i++)
    {
        if (m_pAVFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_nVideoIndex = i;
        }
        else if (m_pAVFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            m_nAudioIndex = i;
        }

        printf("m_pAVFmtCxt->streams[i]->codec->codec_type:%d", m_pAVFmtCxt->streams[i]->codecpar->codec_type);
        if (m_bSupportAudio && m_pAVFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            //根据输入流配置创建输出流,指定音频输出编码为pcmu
            if (m_bSupportMike)
            {
   
                in_stream = m_pAVFmtCxt->streams[i];

                auto pAudioCodec = avcodec_find_encoder(AV_CODEC_ID_PCM_MULAW);
                if (pAudioCodec == NULL) {
                    printf("Cannot find any endcoder");
                    continue;
                }
                AudioCodecCtx = avcodec_alloc_context3(pAudioCodec);
                if (AudioCodecCtx == NULL) {
                    printf("Cannot alloc context");
                    continue;
                }
                out_stream = avformat_new_stream(m_pOutAVFmtCxt, pAudioCodec);
                if (!out_stream)
                {
                    printf("Failed allocating output stream\n");
                    m_bSupportMike = false;
                    continue;
                }

                //设置解码器上下文参数
                AudioCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
                AudioCodecCtx->sample_rate = m_nSampleRate;
                AudioCodecCtx->channels = 1;
                AudioCodecCtx->codec_tag = 0;
                if (m_pOutAVFmtCxt->oformat->flags & AVFMT_GLOBALHEADER)
                    AudioCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
                avcodec_parameters_from_context(out_stream->codecpar, AudioCodecCtx);
                avcodec_free_context(&AudioCodecCtx);
            }
        }
    }

    //查找视频解码器
    m_pCodec = avcodec_find_decoder(m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar->codec_id);
    if (!m_pCodec)
    {
        printf("video decoder not found\n");
        return -1;
    }
    //查找音频解码器
    if (m_bSupportAudio && m_nAudioIndex != -1)
    {
        if (!m_pAudioCodecCxt)
            m_pAudioCodecCxt = avcodec_alloc_context3(NULL);
        auto pAudioCodecpar = m_pAVFmtCxt->streams[m_nAudioIndex]->codecpar;
        avcodec_parameters_to_context(m_pAudioCodecCxt, pAudioCodecpar);
        m_pAudioCodec = avcodec_find_decoder(pAudioCodecpar->codec_id);

        if (m_pAudioCodec == NULL || m_pAudioCodecCxt == NULL)
        {
            printf("audio decoder not found\n");
            return -1;
        }

        //重采样配置
        m_pSwrContext = swr_alloc_set_opts(0,         // 输入为空,则会分配
            av_get_default_channel_layout(m_channels_play),
            AV_SAMPLE_FMT_S16,                         // 输出的格式
            m_nSampleRate,                     // 输出的采样频率
            av_get_default_channel_layout(m_pAudioCodecCxt->channels),
            m_pAudioCodecCxt->sample_fmt,       // 输入的格式
            m_pAudioCodecCxt->sample_rate,      // 输入的采样率
            0,
            0);
        auto ret = swr_init(m_pSwrContext);
        if (ret < 0)
        {
            printf("Failed to swr_init(pSwrContext);");
            return -1;
        }

        //初始化directsound
        if (InitDirectSound() == FALSE)
            m_bSupportAudio = false;
    }

    //视频处理
    m_CodecId = m_pCodec->id;
    if(!m_pCodecCxt)
        m_pCodecCxt = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(m_pCodecCxt, m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar);
    if (m_pCodecCxt)
    {
        if (m_pCodecCxt->width == 0 || m_pCodecCxt->height == 0)
        {
            printf("m_pCodecCxt->width:%d, m_pCodecCxt->height:%d", m_pCodecCxt->width, m_pCodecCxt->height);
            return -1;
        }
    }
    else
        return -1;

    AVCodecContext* temp_codecctx = m_pCodecCxt;
    memcpy(temp_codecctx, m_pCodecCxt, sizeof(m_pCodecCxt));
    if (m_pCodecCxt->codec_type == AVMEDIA_TYPE_VIDEO)
    {
        printf("Soft Solution");
        avcodec_close(m_pCodecCxt);
        m_pCodecCxt = temp_codecctx;
        m_pCodecCxt->thread_count = 4;

        if (m_nD3DRenderWay == D3D_SURFACE) //D3D surface
        {
            if (m_Dxva2D3DRender.InitD3DRender(m_hWnd, m_pCodecCxt->width, m_pCodecCxt->height) == false)
            {
                printf("InitD3DRender fail");
            }

            m_pOutBuffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_pCodecCxt->width, m_pCodecCxt->height, 1));
            if (m_pOutBuffer == NULL)
                return -1;

            av_image_fill_arrays(m_pFrameBGR->data, m_pFrameBGR->linesize, m_pOutBuffer, AV_PIX_FMT_YUV420P, m_pCodecCxt->width, m_pCodecCxt->height, 1); //填充AVFrame数据缓冲
            m_pImgConvertCtx = sws_getContext(m_pCodecCxt->width, m_pCodecCxt->height, m_pCodecCxt->pix_fmt, m_pCodecCxt->width, m_pCodecCxt->height, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
            if (m_pImgConvertCtx == NULL)
                return -1;
        }
    
        m_nActualWidth = m_pCodecCxt->width;
        m_nActualHeight = m_pCodecCxt->height;

        res = avcodec_open2(m_pCodecCxt, m_pCodec, NULL);
        if (res < 0)
        {
            printf("avcodec_open2 video fail  error:%x", res);
            return -1;
        }
    }

    // 输入音频的处理
    if (m_bSupportAudio && m_pAudioCodecCxt)
    {
        if (m_pAudioCodecCxt->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            res = avcodec_open2(m_pAudioCodecCxt, m_pAudioCodec, NULL);
            if (res < 0)
            {
                printf("avcodec_open2 audio fail  error:%x", res);
                avcodec_close(m_pAudioCodecCxt);
                return -1;
            }
        }
    }

    // 输出音频的处理
    if (m_bSupportMike)
    {
        if (m_CaptureAudio.Open()) //打开麦克风
        {
            m_bresampleinitsuc = audio_resample_init();
            if (!m_bresampleinitsuc)
            {
                m_bSupportMike = false;
            }
            m_CaptureAudio.SetGrabAudioFrames(TRUE, this); //采集音频
        }
        else
        {
            m_bSupportMike = false;
        }
    }

    printf("Start ThreadDecode...");

    //创建解码线程
    m_pDecodeThread = (my_thread_t*)malloc(sizeof(my_thread_t));
    if (m_pDecodeThread != NULL)
    {
        m_bDecodeThreadRun = true;
        res = my_thread_create(m_pDecodeThread, ThreadDecode, this);
        if (res == -1)
        {
            printf("my_thread_create ThreadDecode failed  res:%x", res);
            return -1;
        }
    }

    return 0;
}

2.码流解码处理 (码流客户端)
void ClientPlayer::DecodeStream()
{
    printf("DecodeAndShow");
    AVPacket pkt = { 0 };
    m_bPlaying = true;
    uint8_t* pBuffer;
    bool bEnoughSpace = true;
    int nTimeCnt = 0;
    int res = 0;
    int nRectDrawWait = 0;
    bool bRecordLastIFrame = false;
    int num_av_read_frame_err = 0;
    int num_stream_index_err = 0;
    uint8_t * outData[2] = {0};
    outData[0] = (uint8_t*)av_malloc(1152 * 8);
    outData[1] = (uint8_t*)av_malloc(1152 * 8);

    uint8_t* pktdata;
    int pktsize;
    int len = 0;
    bool bPushAudioToQueue = false;
    m_bStopAudio = false;
    CRect ShowRect;

    AVFrame* pAvFrame = av_frame_alloc();
    if (pAvFrame == NULL)
        return;


    //创建音频播放线程
    if (m_bSupportAudio && m_pAudioPlayThread)
    {
        my_thread_create(m_pAudioPlayThread, ThreadAudioPlay, this);
    }

    //创建音频对讲线程
    if (m_bSupportMike && m_pAudioTalkThread)
    {
        my_thread_create(m_pAudioTalkThread, ThreadAudioSend, this);
    }

    while (m_bDecodeThreadRun && !m_bQuit) 
    {
        m_lastReadPacktTime = av_gettime();
        if (av_read_frame(m_pAVFmtCxt, &pkt) >= 0) // 读取每一帧数据
        {
            num_av_read_frame_err = 0;
            m_nFrameStartTime = time(NULL);
            if (pkt.stream_index == m_nVideoIndex) //视频流
            {
                num_stream_index_err = 0;
                nTimeCnt = 0;

                if (pkt.flags == 1)//先解视频关键帧,后处理音频
                    bPushAudioToQueue = true;
                if (m_pCodecCxt == NULL || pAvFrame == NULL) {
                    printf("m_pCodecCxt == NULL || pAvFrame == NULL");
                    break;
                }

                int gotvframe = 0;
                auto sd_ret = avcodec_send_packet(m_pCodecCxt, &pkt);
                if (sd_ret != 0 && sd_ret != AVERROR(EAGAIN)) {
                    printf("avcodec_send_packet err, rt=%d", sd_ret);
                    enableReConnect();
                }
                else {
                    while (gotvframe == 0 && !m_bQuit) {
                        gotvframe = avcodec_receive_frame(m_pCodecCxt, pAvFrame);
                        if (gotvframe == 0)
                        {
                            
                            m_nCurPKSize = pkt.size;

                            if (m_pImgConvertCtx && m_pFrameBGR && m_pOutBuffer && pAvFrame)
                            {
                                sws_scale(m_pImgConvertCtx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0,
                                    m_pCodecCxt->height, m_pFrameBGR->data, m_pFrameBGR->linesize);

                                if (m_nD3DRenderWay == D3D_SURFACE) //surface
                                {
                                    //D3D surface视频渲染处理,此处暂不提供
                                }
                            }
                        }
                    }
                }
            }
            else if (pkt.stream_index == m_nAudioIndex) //音频流
            {
                num_stream_index_err = 0;
                if (m_bSupportAudio) {
                    pktdata = pkt.data;
                    pktsize = pkt.size;

                    if (pktsize > 0)
                    {
                        int gotframe = 0;
                        if (m_pAudioCodecCxt == NULL || pAvFrame == NULL) {
                            printf("m_pAudioCodecCxt == NULL || pAvFrame == NULL");
                            break;
                        }
                        len = avcodec_send_packet(m_pAudioCodecCxt, &pkt);
                        if (len != 0 && len != AVERROR(EAGAIN))
                        {
                            pktsize = 0;
                            printf("avcodec_send_packet len < 0");
                            break;
                        }
                        auto data_size = av_get_bytes_per_sample(m_pAudioCodecCxt->sample_fmt); 
                        if (data_size < 0) {
                            printf("Failed to calculate data size\n");
                            break;
                        }
                        while (gotframe == 0 && !m_bQuit) {
                            gotframe = avcodec_receive_frame(m_pAudioCodecCxt, pAvFrame);
                            if (!gotframe)
                            {

                                if (bPushAudioToQueue == true && m_bEnableAudio && !m_bStopAudio)
                                {
                                    audio_frame_t audioFrame;

                                    // 获取每一个采样点的字节大小
                                    numBytes = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
                                    //uint8_t outData[2][192000] = { 0 };
                                    // 修改采样率参数后,须要从新获取采样点的样本个数
                                    auto dstNbSamples = av_rescale_rnd(pAvFrame->nb_samples,
                                        m_src_sample_rate,
                                        pAvFrame->sample_rate,
                                        AV_ROUND_ZERO);
                                      
                                    // 重采样处理
                                    int data_size = 0;
                                    try
                                    {
                                        auto nb = swr_convert(m_pSwrContext,
                                            (uint8_t**)outData,
                                            dstNbSamples,
                                            (const uint8_t**)pAvFrame->data,
                                            pAvFrame->nb_samples);
                                        data_size = av_samples_get_buffer_size(nullptr, m_channels_play, nb, AV_SAMPLE_FMT_S16, 1);
                                    }
                                    catch (const std::exception&)
                                    {
                                        m_bSupportAudio = false;
                                        printf("swr_convert throw err, set m_bSupportAudio false");
                                        continue;
                                    }

                                    int copy_size = 0; 
                                    int copy_ptr = 0;                              
                                    for (int isub = data_size; isub > 0; isub -= copy_size) {
                                        if (isub > m_audio_buffer_notify_size) {
                                            copy_size = m_audio_buffer_notify_size;
                                            copy_ptr = data_size - isub;
                                        }
                                        else
                                            copy_size = isub;
                                        audioFrame.data_size = copy_size;
                                        memcpy(audioFrame.data, outData[0] + copy_ptr, copy_size);

                                        //线程锁,音频播放缓冲队列存储音频帧数据
                                        EnterCriticalSection(&m_lock);
                                        m_AudioPlayQue.push(audioFrame);
                                        LeaveCriticalSection(&m_lock);
                                    }
                                }

                            }
                        }
                    }
                }
            }
            else if (++num_stream_index_err > 20) {
                printf("pkt.stream_index unfind, %d",pkt.stream_index);
                enableReConnect();
            }

            av_packet_unref(&pkt);
        }
        else {
            if (++num_av_read_frame_err > 10) {
                printf("num_av_read_frame_err is more than 10");
                enableReConnect();
            }
        }
    }

    if (pAvFrame)
        av_free(pAvFrame);

    if (pBuffer)
        av_free(pBuffer);

    if (outData[0] && outData[1])
    {
        av_free(outData[0]);
        av_free(outData[1]);
        outData[0] = 0;
        outData[1] = 0;
    }
}

3.音频播放线程 (码流客户端)
void ClientPlayer::AudioPlayThread()
{
    bool bPlay = true;
    LPVOID buf = NULL;
    DWORD  buf_len = 0;
    DWORD res = WAIT_OBJECT_0;

    DWORD offset = m_audio_buffer_notify_size;

    if (!m_pDSBuffer8)
        return;
    m_pDSBuffer8->SetCurrentPosition(0);
    m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);
    while (m_bPlaying && m_bSupportAudio)
    {
        if (!m_bEnableAudio || m_bStopAudio || m_bPause)
        {
            while (!m_AudioPlayQue.empty())
            {
                m_AudioPlayQue.pop();
            }
            if (m_pDSBuffer8 && bPlay)
            {
                m_pDSBuffer8->Stop();
                m_pDSBuffer8->Restore();
                m_pDSBuffer8->SetCurrentPosition(0);
            }
            bPlay = false;
            continue;
        }
        else
        {
            if (bPlay == false) {
                m_pDSBuffer8->SetCurrentPosition(0);
                m_pDSBuffer8->Play(0, 0, DSBPLAY_LOOPING);
                bPlay = true;
            }
            if ((res >= WAIT_OBJECT_0) && (res <= WAIT_OBJECT_0 + 3))
            {
                m_pDSBuffer8->Lock(offset, m_audio_buffer_notify_size, &buf, &buf_len, NULL, NULL, 0);
                if (buf_len > 0) {
                    if (m_AudioPlayQue.size() > 0) {

                        audio_frame_t& audioFrame = m_AudioPlayQue.front();

                        int cpy_num = buf_len < audioFrame.data_size ? buf_len : audioFrame.data_size;
                        memcpy(buf, audioFrame.data, cpy_num);

                        if (audioFrame.data_size <= buf_len) {
                            EnterCriticalSection(&m_lock);
                            printf("audioFrame.data_size:%d", audioFrame.data_size);
                            m_AudioPlayQue.pop();
                            LeaveCriticalSection(&m_lock);
                        }
                        else {
                            audioFrame.data_size = audioFrame.data_size - buf_len;
                            uint8_t data[2048];
                            memcpy(data, audioFrame.data + cpy_num, audioFrame.data_size);
                            memcpy(audioFrame.data, data, audioFrame.data_size);
                        }

                        offset += cpy_num;
                        offset %= (m_audio_buffer_notify_size * MAX_AUDIO_BUF);

                        int buf_short_len = cpy_num * sizeof(char) / sizeof(short);
                        const int out_len = 80;

                    }
                    else {
                        memset(buf, 0, buf_len);
                        offset += buf_len;
                        offset %= (m_audio_buffer_notify_size * MAX_AUDIO_BUF);
                    }
                }
                m_pDSBuffer8->Unlock(buf, buf_len, NULL, 0);
            }
        }
        res = WaitForMultipleObjects(MAX_AUDIO_BUF, m_event, FALSE, 1000/*INFINITE*/);
    }

    printf("PlayAudioThread thread stop...");
}
3.音频发送线程 (码流客户端)
void ClientPlayer::AudioSendThread()
{
    int ret = 0;
    int index = 0;

    if (!(m_pOutAVFmtCxt->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&m_pOutAVFmtCxt->pb, m_url_speak.c_str(), AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("avio_open fail ret:%d", ret);
            return;
        }
    }
    AVDictionary* dict = NULL;
    av_dict_set(&dict, "rtsp_transport", "udp", 0);
    av_dict_set(&dict, "muxdelay", "0.1", 0);
    m_pOutAVFmtCxt->audio_codec_id = m_pOutAVFmtCxt->oformat->audio_codec;
    ret = avformat_write_header(m_pOutAVFmtCxt, NULL);
    if (ret < 0)
    {
        printf("avformat_write_header fail ret:%d\n", ret);
        return;
    }

    while (m_bPlaying && m_bSupportMike)
    {
        if (!m_bEnableMike)
        {
            Sleep(200);
            while (!m_AudioTalkQue.empty())
            {
                EnterCriticalSection(&m_talklock);
                m_AudioTalkQue.pop();
                LeaveCriticalSection(&m_talklock);
            }
            continue;
        }

        if (m_AudioTalkQue.size() > 0)
        {
            talk_frame_t talkFrame = m_AudioTalkQue.front();
            char g711Buffer[2048];
            long g711Size = talkFrame.data_size / 2;
            Pcm2G711(talkFrame.data, talkFrame.data_size, g711Buffer, g711Size, PCM_U);

            auto pPkt = av_packet_alloc();
            AVPacket& pkt = *pPkt;
            av_init_packet(&pkt);
            pkt.data = (uint8_t*)g711Buffer;
            pkt.size = g711Size;
            pkt.stream_index = 0;

            pkt.pts = pkt.dts = index;
            index++;
            pkt.duration = 1;
            int ret = av_interleaved_write_frame(m_pOutAVFmtCxt, &pkt);
            if (ret < 0)
            {
                printf("av_interleaved_write_frame fail ret:%d", ret);
            }
            if (!m_AudioTalkQue.empty())
            {
                EnterCriticalSection(&m_talklock);
                m_AudioTalkQue.pop();
                LeaveCriticalSection(&m_talklock);
            }

            //av_free_packet(&pkt);
            av_packet_free(&pPkt);
        }
        else {
            Sleep(20);
        }
    }

    av_write_trailer(m_pOutAVFmtCxt); 

    if (m_pOutAVFmtCxt && !(m_pOutAVFmtCxt->oformat->flags & AVFMT_NOFILE))
        avio_close(m_pOutAVFmtCxt->pb);

    avformat_free_context(m_pOutAVFmtCxt);
}

4. 初始化Directsound(码流客户端)
//directsound 初始化
BOOL ClientPlayer::InitDirectSound()
{
    int channels = m_channels_play;
    int bits_per_sample = m_bits_per_sample_play;

    if (m_bSupportAudio)
    {
        if (m_nPlatForm == PLATFORM_TYPE_XS7300 || m_pAVFmtCxt->streams[m_nAudioIndex]->codecpar->codec_id == AV_CODEC_ID_AAC)
        {
            m_audio_buffer_notify_size = BUFFERNOTIFYSIZE_XS7300;
        }
        else
        {
            m_audio_buffer_notify_size = BUFFERNOTIFYSIZE_HISI;
        }

        if (FAILED(DirectSoundCreate8(NULL, &m_pDS, NULL)))
        {
            return FALSE;
        }

        HWND hDlg = GetForegroundWindow()->GetSafeHwnd();
        if (!hDlg) {
            hDlg = GetDesktopWindow()->GetSafeHwnd();
        }

        if (FAILED(m_pDS->SetCooperativeLevel(hDlg, DSSCL_NORMAL)))
        {
            printf("[SetCooperativeLevel Faile]\n");
            return FALSE;
        }
    }

    DSBUFFERDESC dsbd;
    memset(&dsbd, 0, sizeof(dsbd));
    dsbd.dwSize = sizeof(dsbd);
    dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
    dsbd.dwBufferBytes = MAX_AUDIO_BUF * m_audio_buffer_notify_size;
    dsbd.lpwfxFormat = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));
    dsbd.lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM;
    dsbd.lpwfxFormat->nChannels = channels;//format type         
    dsbd.lpwfxFormat->nSamplesPerSec = m_nSampleRate;//number of channels (i.e. mono, stereo...)     
    dsbd.lpwfxFormat->nAvgBytesPerSec = m_nSampleRate * (bits_per_sample / 8) * channels;//sample rate
    dsbd.lpwfxFormat->nBlockAlign = (bits_per_sample / 8) * channels;//for buffer estimation      
    dsbd.lpwfxFormat->wBitsPerSample = bits_per_sample;//block size of data     
    dsbd.lpwfxFormat->cbSize = 0;//number of bits per sample of mono data 

    if (FAILED(m_pDS->CreateSoundBuffer(&dsbd, &m_pDSBuffer, NULL)))
    {
        printf("create m_pDSBuffer failed \n");
        return FALSE;
    }

    if (FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&m_pDSBuffer8)))
    {
        printf("create m_pDSBuffer8 failed \n");
        return FALSE;
    }
    if (FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&m_pDSNotify)))
    {
        printf("create m_pDSNotify failed \n");
        return FALSE;
    }

    while (!m_AudioPlayQue.empty())
    {
        m_AudioPlayQue.pop();
    }
    if (m_pDSBuffer8)
    {
        m_pDSBuffer8->Stop();
        m_pDSBuffer8->Restore();
    }

    for (int i = 0; i < MAX_AUDIO_BUF; i++)
    {
        m_pDSPosNotify[i].dwOffset = i * m_audio_buffer_notify_size;
        m_event[i] = ::CreateEvent(NULL, false, false, NULL);
        m_pDSPosNotify[i].hEventNotify = m_event[i];
    }

    m_pDSNotify->SetNotificationPositions(MAX_AUDIO_BUF, m_pDSPosNotify);
    m_pDSNotify->Release();

    return TRUE;
}

5. 音频采集封装类 (码流客户端)

mycaptureaudio.h

#include <mmsystem.h>
#include <dsound.h>
#include "wavefile.h"
#include "G711Codec.h"

#define NUM_REC_NOTIFICATIONS  16
class CAudioFrameHandler 
{

public:
    virtual void CaptureAudioFrameData(uint8_t* pBuffer, long lBufferSize) = 0; 

};
class MyCaptureAudio
{

public:
    BOOL m_bGrabAudioFrame;                 //recording now ? also used by event recv thread

    protected:
    LPDIRECTSOUNDCAPTURE8 m_pCapDev;        //设备对象指针 
    LPDIRECTSOUNDCAPTUREBUFFER m_pCapBuf;   //缓冲区对象指针
    LPDIRECTSOUNDNOTIFY8 m_pNotify;         //用来设置通知的对象接口
    GUID m_guidCapDevId;                    //设备id
    WAVEFORMATEX m_wfxInput;                //输入的音频格式
    CWaveFile *m_pWaveFile;                 //wave 文件指针
    bool m_bCanWav;                         //wave 文件是否可写
    DSBPOSITIONNOTIFY m_aPosNotify[NUM_REC_NOTIFICATIONS + 1]; //设置通知标志的数组 
    HANDLE m_hNotifyEvent;                  //通知事件
    BOOL m_abInputFmtSupported[20];
    DWORD m_dwCapBufSize;                   //录音用缓冲区的大小
    DWORD m_dwNextCapOffset;                //偏移位置 
    DWORD m_dwNotifySize;                   // 通知位置
    CAudioFrameHandler* m_frame_handler;    // outer frame data dealer ptr 

public:                                 
    static BOOL CALLBACK CbEnumDevProc(LPGUID lpGUID, LPCTSTR lpszDesc, 
    LPCTSTR lpszDrvName, LPVOID lpContext); // callback func to add enum devices string name 
    static UINT NotifyCaptureThread(LPVOID data);

    protected:
    HRESULT InitDirectSoundCapture(); 
    HRESULT DeInitDirectSoundCapture(); 
    HRESULT InitNotifications(); 
    HRESULT CreateCaptureBuffer(WAVEFORMATEX * wfx); 
    HRESULT SetStartCaptureData(BOOL bStartRec);
    HRESULT DoCapturedData(); 
    void SetWavFormat(WAVEFORMATEX * wfx);

public:
    MyCaptureAudio(void);
    ~MyCaptureAudio(void);
    BOOL EnumDevices(HWND hList);
    BOOL Open(void); 
    BOOL Close(); 
    void SetGrabAudioFrames(BOOL bGrabAudioFrames, CAudioFrameHandler* frame_handler); 

}; 

mycaptureaudio.cpp

#include "stdafx.h"
#include "mycaptureaudio.h"
#include <mmsystem.h>
#include <dsound.h>

#ifndef SAFE_DELETE
#define SAFE_DELETE(p)  {if(p) {delete (p);(p)=NULL;}}
#endif

#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) {if(p) {(p)->Release(); (p)=NULL;}}
#endif

#ifndef MAX
#define MAX(a,b)        ((a) > (b) ? (a) : (b))
#endif

MyCaptureAudio::MyCaptureAudio(void)
{
    // 1.初始化 CoInitialize();
    if(FAILED(CoInitialize(NULL))) 
    {
        printf("MyCaptureAudio CoInitialize Failed!\r\n"); 
        return;
    }
    m_pCapDev = NULL;
    m_pCapBuf = NULL; 
    m_pNotify = NULL;
    
    // set default wave format PCM
    ZeroMemory(&m_wfxInput, sizeof(m_wfxInput));
    m_wfxInput.wFormatTag = WAVE_FORMAT_PCM; //输入的音频格式
    m_guidCapDevId = GUID_NULL; //设备id
    m_bGrabAudioFrame = FALSE; 
    m_hNotifyEvent = NULL; 
    m_bCanWav = false;

}

MyCaptureAudio::~MyCaptureAudio(void)
{
    CoUninitialize() ; 
}

BOOL CALLBACK MyCaptureAudio::CbEnumDevProc(LPGUID lpGUID, LPCTSTR lpszDesc, 
             LPCTSTR lpszDrvName, LPVOID lpContext) 
{
    HWND hList = (HWND)lpContext;
    if(!hList) 
        return FALSE; 
    LPGUID lpTemp = NULL;
    if(lpGUID != NULL) 
    {
        // NULL only for "Primary Sound Driver".
        if ((lpTemp = (LPGUID)malloc(sizeof(GUID))) == NULL) 
            return(TRUE);
        memcpy(lpTemp, lpGUID, sizeof(GUID));
    }
    ::SendMessage(hList, CB_ADDSTRING, 0,(LPARAM)lpszDesc);
    ::SendMessage(hList, LB_SETITEMDATA, 0, (LPARAM)lpTemp); 
    free(lpTemp);
    return(TRUE);
}

UINT MyCaptureAudio::NotifyCaptureThread(LPVOID data)
{
    MyCaptureAudio * pado = static_cast<MyCaptureAudio *>(data); 
    MSG   msg;
    HRESULT hr; 
    DWORD dwResult; 
    while(pado->m_bGrabAudioFrame) 
    {
        // 6.等待设置事件发生
        dwResult = MsgWaitForMultipleObjects(1, &(pado->m_hNotifyEvent), FALSE, INFINITE, QS_ALLEVENTS );
        switch( dwResult ) 
        {
            case WAIT_OBJECT_0 + 0:
                // g_hNotificationEvents[0] is signaled
                // This means that DirectSound just finished playing 
                // a piece of the buffer, so we need to fill the circular 
                // buffer with new sound from the wav file
                if(FAILED(hr = pado->DoCapturedData())) 
                {
                    printf("Error handling DirectSound notifications."); 
                    pado->m_bGrabAudioFrame = FALSE ; 
                }
            break;
            case WAIT_OBJECT_0 + 1:
                // Windows messages are available
                while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
                { 
                    TranslateMessage(&msg); 
                    DispatchMessage(&msg); 
                    if(msg.message == WM_QUIT) 
                        pado->m_bGrabAudioFrame = FALSE; 
                }
            break;
        }
    }
    AfxEndThread(0, TRUE); 
    return 0; 
}

BOOL MyCaptureAudio::EnumDevices(HWND hList) 
{
    if(FAILED(DirectSoundCaptureEnumerate(
    (LPDSENUMCALLBACK)(MyCaptureAudio::CbEnumDevProc),
    (VOID*)&hList)))
    {
        return(FALSE);
    }
    return (TRUE); 
}
BOOL MyCaptureAudio::Open(void)
{
    printf("MyCaptureAudio::Open 000");
    HRESULT hr; 
    if(!m_bGrabAudioFrame) 
    {
        hr = InitDirectSoundCapture(); 
    }
    return (FAILED(hr)) ? FALSE : TRUE; 
}

BOOL MyCaptureAudio::Close() 
{
    HRESULT hr; 
    hr = DeInitDirectSoundCapture(); 
    if (m_hNotifyEvent) {
        CloseHandle(m_hNotifyEvent);
        m_hNotifyEvent = NULL;
    }
    return (FAILED(hr)) ? FALSE : TRUE; 
}

HRESULT MyCaptureAudio::InitDirectSoundCapture()
{
    printf("InitDirectSoundCapture");
    HRESULT hr; 
    //m_guidCapDevId = dev_id;
    ZeroMemory(&m_aPosNotify, sizeof(DSBPOSITIONNOTIFY) * (NUM_REC_NOTIFICATIONS + 1));
    m_dwCapBufSize = 0;
    m_dwNotifySize = 0;

    // 2.创建设备
    // Create IDirectSoundCapture using the preferred capture device

    //hr = DirectSoundCaptureCreate(&m_guidCapDevId, &m_pCapDev, NULL);
    hr = DirectSoundCaptureCreate(&DSDEVID_DefaultCapture, &m_pCapDev, NULL);

    // init wave format 
    SetWavFormat(&m_wfxInput);

    #ifdef WAV_TEST_PCM
    m_pWaveFile = new CWaveFile();
    if(m_pWaveFile->OpenFile("pcmtest2.wav", m_wfxInput) == true)
    {
        printf("Open test.wav fail");
        m_bCanWav = true;
    }
    #endif

    return (FAILED(hr)) ? E_FAIL : S_OK; 
}
HRESULT MyCaptureAudio::DeInitDirectSoundCapture()
{
    // Release DirectSound interfaces
    SAFE_RELEASE(m_pNotify);
    SAFE_RELEASE(m_pCapBuf);
    SAFE_RELEASE(m_pCapDev); 

    #ifdef WAV_TEST_PCM
    m_bCanWav = false;
    if(m_pWaveFile)
        m_pWaveFile->CloseFile();
    SAFE_DELETE(m_pWaveFile);
    #endif

    return S_OK;
}
HRESULT MyCaptureAudio::CreateCaptureBuffer(WAVEFORMATEX * wfx) 
{
    HRESULT hr;
    DSCBUFFERDESC dscbd;
    SAFE_RELEASE(m_pNotify);
    SAFE_RELEASE(m_pCapBuf);

    // Set the notification size
    m_dwNotifySize = MAX(1024, wfx->nAvgBytesPerSec / 8);//  44100 16bit  has 11025
    m_dwNotifySize -= m_dwNotifySize % wfx->nBlockAlign;

    // Set the buffer sizes 
    m_dwCapBufSize = m_dwNotifySize * NUM_REC_NOTIFICATIONS;
    SAFE_RELEASE(m_pNotify);
    SAFE_RELEASE(m_pCapBuf);

    // Create the capture buffer
    ZeroMemory(&dscbd, sizeof(dscbd));
    dscbd.dwSize        = sizeof(dscbd);
    dscbd.dwBufferBytes = m_dwCapBufSize;
    dscbd.lpwfxFormat   = wfx ; // Set the format during creatation

    if(NULL == m_pCapDev)
        return E_FAIL;

    if(FAILED(hr = m_pCapDev->CreateCaptureBuffer(&dscbd, &m_pCapBuf, NULL)))
        return S_FALSE;

    m_dwNextCapOffset = 0;
    if(FAILED(hr = InitNotifications()))
        return S_FALSE;

    return S_OK;
}
HRESULT MyCaptureAudio::InitNotifications() 
{
    HRESULT hr; 
    int i; 
    if(NULL == m_pCapBuf)
    return S_FALSE;

    // 4.1 创建事件
    // create auto notify event 
    m_hNotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    // 4.2.query一个LPDIRECTSOUNDCAPTUREBUFFER8类型的对象
    // Create a notification event, for when the sound stops playing
    if(FAILED(hr = m_pCapBuf->QueryInterface(IID_IDirectSoundNotify,(VOID**)&m_pNotify)))
        return S_FALSE ;
        
    // Setup the notification positions
    for(i = 0; i < NUM_REC_NOTIFICATIONS; i++) 
    {
        m_aPosNotify[i].dwOffset = (m_dwNotifySize * i) + m_dwNotifySize - 1;
        m_aPosNotify[i].hEventNotify = m_hNotifyEvent;             
    }

    // 4.3 设置事件提醒
    // Tell DirectSound when to notify us. the notification will come in the from 
    // of signaled events that are handled in WinMain()
    if(FAILED(hr = m_pNotify->SetNotificationPositions(NUM_REC_NOTIFICATIONS, m_aPosNotify)))
        return S_FALSE ;
    return S_OK;
}
HRESULT MyCaptureAudio::SetStartCaptureData(BOOL bStartRec)
{
    HRESULT hr;
    if(bStartRec) 
    {
        printf("SetStartCaptureData start");
        // 3.创建buffer对象
        // Create a capture buffer, and tell the capture 
        // buffer to start recording   
        if(FAILED(hr = CreateCaptureBuffer(&m_wfxInput)))
            return S_FALSE ;

        // 5.启动录音
        if(FAILED(hr = m_pCapBuf->Start(DSCBSTART_LOOPING)))
            return S_FALSE ;

            
        // create notify event recv thread 
        //AfxBeginThread 函数时基于MFC的,在Windows APi提供的CreateThread 函数的基础上再封装,自动释放句柄
        AfxBeginThread(MyCaptureAudio::NotifyCaptureThread, (LPVOID)(this));

    } 
    else 
    { 
        printf("SetStartCaptureData stop");
        // Stop the capture and read any data that 
        // was not caught by a notification
        if(NULL == m_pCapBuf)
            return S_OK;
            
        // wait until the notify_event_thd thread exit and release the resources.
        Sleep(500) ;

        // Stop the buffer, and read any data that was not 
        // caught by a notification
        if(FAILED(hr = m_pCapBuf->Stop()))
            return S_OK;
        if(FAILED(hr = DoCapturedData()))
            return S_FALSE; 
    }
    return S_OK;
}
HRESULT MyCaptureAudio::DoCapturedData() 
{
     HRESULT hr;
     VOID*   pbCaptureData    = NULL;
     DWORD   dwCaptureLength;
     VOID*   pbCaptureData2   = NULL;
     DWORD   dwCaptureLength2;
     DWORD   dwReadPos;
     DWORD   dwCapturePos;
     LONG lLockSize;
     if(NULL == m_pCapBuf)
        return S_FALSE; 
     
     if(FAILED(hr = m_pCapBuf->GetCurrentPosition(&dwCapturePos, &dwReadPos)))
        return S_FALSE;
     lLockSize = dwReadPos - m_dwNextCapOffset;
     if(lLockSize < 0)
        lLockSize += m_dwCapBufSize;

     //  1 锁操作lock
     // Block align lock size so that we are always write on a boundary
     lLockSize -= (lLockSize % m_dwNotifySize);
     if(lLockSize == 0)
        return S_FALSE;
        
     // Lock the capture buffer down
     if(FAILED(hr = m_pCapBuf->Lock( m_dwNextCapOffset, lLockSize,
               &pbCaptureData, &dwCaptureLength, 
               &pbCaptureData2, &dwCaptureLength2, 0L)))
        return S_FALSE;

     //  2 存储到wav文件
     #ifdef WAV_TEST_PCM
     if(m_bCanWav && m_pWaveFile)
     {
        m_pWaveFile->WriteDataToFile((LPSTR)pbCaptureData, dwCaptureLength);
     }
     #endif

      //  3 拷贝数据
     // call the outer data handler
     if(m_frame_handler)
     {
        m_frame_handler->CaptureAudioFrameData((uint8_t*)pbCaptureData, dwCaptureLength); 
     }
     
     // Move the capture offset along
     m_dwNextCapOffset += dwCaptureLength; 
     m_dwNextCapOffset %= m_dwCapBufSize; // Circular buffer
     if(pbCaptureData2 != NULL) 
     {
        // call the outer data handler 
        if(m_frame_handler) 
        {
            m_frame_handler->CaptureAudioFrameData((uint8_t*)pbCaptureData, dwCaptureLength); 
        }
        // Move the capture offset along
        m_dwNextCapOffset += dwCaptureLength2; 
        m_dwNextCapOffset %= m_dwCapBufSize; // Circular buffer
     }

     //  4 锁操作unlock
     // Unlock the capture buffer
     m_pCapBuf->Unlock( pbCaptureData, dwCaptureLength, pbCaptureData2, dwCaptureLength2);
     return S_OK;
}
void MyCaptureAudio::SetWavFormat(WAVEFORMATEX * wfx)
{
    // get the default capture wave formate 
    ZeroMemory(wfx, sizeof(WAVEFORMATEX)); 
    wfx->wFormatTag = WAVE_FORMAT_PCM;
    
    // 44100Hz(CD)/48000Hz(DVD), 16 bits PCM, Mono
    wfx->nSamplesPerSec = 44100; 
    wfx->wBitsPerSample = 16; 
    wfx->nChannels  = 1;
    wfx->nBlockAlign = wfx->nChannels*(wfx->wBitsPerSample/8); 
    wfx->nAvgBytesPerSec = wfx->nBlockAlign*wfx->nSamplesPerSec;
}
void MyCaptureAudio::SetGrabAudioFrames(BOOL bGrabAudioFrames, CAudioFrameHandler* frame_handler) 
{
    m_frame_handler = frame_handler; 
    m_bGrabAudioFrame = bGrabAudioFrames; 
    SetStartCaptureData(bGrabAudioFrames); 
}

6. 码流服务器处理

在RTSP Record中获取到对讲流会话,寻找带speak的会话,对speak会话进行接收处理,接收到rtp数据后,解rtp包获取到音频数据,将pcmu转为pcm,发送到播放设备中。

int rtsp_handle_record(rtsp_request_t* req, const char* uri)
{
    ...

    // 寻找带speak的会话
    if(strstr(path, "speak") != NULL)
    {
        ts_speak = (media_session_t *)req->media_session[3];
        if(!ts_speak)
        {
            rtsp_request_reply(req, RTSP_RESP_SESSION_NOT_FOUND, NULL);
            return -1;
        }
    }

    ...

    req->state = REQ_STATE_START;
    // 开始对speak会话进行接收处理
    if(ts_speak) 
    {
        if(session_recv_start(ts_speak) == -1)
        {
            req->state = REQ_STATE_STOP;
            return -1;
        }
    }

    return 0;
}

int session_recv_start(media_session_t *ts)
{
    
    if(!ts->ev_speek)
    {
        sock_set_noblk(ts->sock->rtp_fd);
        ts->ev_speek = event_create(ts->sock->rtp_fd, 0,  on_session_recv,  
                                    NULL,  on_session_error, ts);
        if (-1 == event_add(ts->evbase, ts->ev_speek)) 
        {
            event_destroy(ts->ev_speek);
            ts->ev_speek = NULL;    
            return -1;
        }
    }

    return 0;
}

//socket接收音频流数据
static void on_session_recv(int fd, void *arg)
{

    media_session_t *ts = (media_session_t *)arg;
    int rlen = 0;
    uint8_t buf[2048] = {0};
    
    len = sock_recv2(ts->sock->rtp_fd, buf, sizeof(buf)-1, 0);       
    if(len > 0)
    {
        push_audio(buf, len);
    }
}

void push_audio(void* packet, int bytes)
{
    if(packet == NULL || bytes <= 12 || bytes > AUDIO_BUF_SIZE) //注意rtp头部有12字节
    {
        return;
    }

    // 1. 解rtp包获取到音频数据
    rtp_packet_t pkt = {0};
    if (0 != rtp_packet_deserialize(&pkt, packet, bytes))
    {
        return;
    }

    // 2. 接收音频数据,同时将pcmu转为pcm
    int i = 0;
    int msgid = stream_audio_get_msgid();
    audio_msg_t stMsg = {0};
    stMsg.mtype = RTP_AUDIO_MSGTYPE;
    stMsg.lenth = min(pkt.payloadlen, sizeof(stMsg.buf)); // 跳过rtp头部
    for(i = 0; i < stMsg.lenth; i++)
    {
        stMsg.buf[i] = (short)_mu2lin[*(((uint8_t*)pkt.payload)+i)];
    }

    // 3. 发送数据到播放设备中
    ...
}


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅笑一斤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值