Linux下C语言实现ffmpeg 视频+音频拉流

6 篇文章 1 订阅
5 篇文章 0 订阅

Linux下C语言实现ffmpeg 视频+音频拉流

1.环境需求
①ffmpeg源码编译
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu官网编译教程
一定要支持h264

②alsa支持

2.源码

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <pthread.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <errno.h>
#include <unistd.h>
#include <math.h>
#define CHANNELS 2
#define FSIZE 2*CHANNELS

void main()
{
    //初始化网络
    avformat_network_init();
    //初始化设备
    avdevice_register_all();

    //服务器地址
    char *in_name="rtmp://127.0.0.1/live/stream";

    AVFormatContext* infmt_ctx=NULL;
    //创建输入封装器
    ret=avformat_open_input(&infmt_ctx, in_name, NULL, NULL);
    if (ret != 0)
    {
        printf("failed alloc output context\n");
        return ;
    }

    //读取一部分视音频流并且获得一些相关的信息
    avformat_find_stream_info(infmt_ctx, NULL);

    videoindex=-1,audioindex=-1,audiostop=0;
    //查找视频||音频流
    for(int i=0; i<infmt_ctx->nb_streams; i++)
    {
        if(infmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            videoindex=i;
        }
        else if(infmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
        {
            audioindex=i;
        }
    }

    printf("videoindex:%d,audioindex:%d\n");
    fflush(stdout);
    if (videoindex == -1)
    {
        printf("input video stream not exist\n");
        return ;
    }
    if (audioindex == -1)
    {
        printf("input audio stream not exist\n");
        return ;
    }
    
    //视频解码器
    AVCodec* decodec = NULL;
    //视频解码器上下文
    AVCodecContext* decodec_ctx = NULL;

    decodec_ctx=infmt_ctx->streams[videoindex]->codec;
    //找到视频解码器
    decodec = avcodec_find_decoder(decodec_ctx->codec_id);
    if (!decodec)
    {
        printf("not find decoder\n");
        avformat_close_input(&infmt_ctx);
        return ;
    }

    //打开视频解码器
    ret = avcodec_open2(decodec_ctx, decodec, NULL);
    if (ret < 0) {
        printf("Could not open codec:\n");
        return ;
    }
    //音频解码器
    AVCodec* decodec2 = NULL;
    //音频解码器上下文
    AVCodecContext* decodec_ctx2 = NULL;

    decodec_ctx2=infmt_ctx->streams[audioindex]->codec;
    //找到音频解码器
    decodec2 = avcodec_find_decoder(decodec_ctx2->codec_id);
    if (!decodec2)
    {
        printf("not find decoder\n");
        avformat_close_input(&infmt_ctx);
        return ;
    }

    //打开音频解码器
    ret = avcodec_open2(decodec_ctx2, decodec2, NULL);
    if (ret < 0) {
        printf("Could not open codec:\n");
        return ;
    }

    //查看输入封装内容
    av_dump_format(infmt_ctx, 0, in_name,0);

    AVFrame *pFrame,*pFrameRGB;
    //视频原始帧
    pFrame = av_frame_alloc();
    //视频目的帧
    pFrameRGB = av_frame_alloc();
    pFrameRGB->format = AV_PIX_FMT_RGB24;
    pFrameRGB->width = 640;
    pFrameRGB->height = 480;
    ret = av_frame_get_buffer(pFrameRGB, 1);
    if (ret != 0)
    {
        printf("fail to frame get buffer\n");
    }

    //RGB24转换器
    struct SwsContext *rgb_convert_ctx  = NULL;
    sws_getCachedContext(rgb_convert_ctx, 
                         decodec_ctx->width, 
                         decodec_ctx->height,
                         decodec_ctx->pix_fmt,
                         640,
                         480, 
                         AV_PIX_FMT_RGB24, 
                         SWS_BICUBIC, 0, 0, 0);
    if (!rgb_convert_ctx)
    {
        printf("fail to sws_getCachedContext\n");
    }


    //音频AAC->PCM转换器
    struct SwrContext *pcm_convert_ctx  = swr_alloc();
    if (!pcm_convert_ctx)
    {
        fprintf(stderr, "Could not allocate resampler context\n");
        return ;
    }
    swr_alloc_set_opts(pcm_convert_ctx,
                       AV_CH_LAYOUT_STEREO,
                       AV_SAMPLE_FMT_S16,
                       44100,
                       AV_CH_LAYOUT_STEREO,
                       AV_SAMPLE_FMT_FLTP,
                       44100,
                       0,
                       NULL);


    if ((ret = swr_init(pcm_convert_ctx)) < 0)
    {
        fprintf(stderr, "Failed to initialize the resampling context\n");
        return ;
    }

    //音视频流包裹
    AVPacket *dec_pkt;
    dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));

    int got_picture=0;

    int rc,err,dir;
    unsigned int val;

    snd_pcm_t *handle;
    //以播放模式打开设备
    rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_PLAYBACK, 0);
    if (rc < 0)
    {
        fprintf(stderr,"unable to open pcm device: %s\n",snd_strerror(rc));
        exit(1);
    }
    //配置硬件参数结构体
    snd_pcm_hw_params_t *params;

    //params申请内存
    snd_pcm_hw_params_malloc(&params);

    //使用pcm设备初始化hwparams
    err=snd_pcm_hw_params_any(handle, params);
    if (err < 0)
    {
        fprintf(stderr, "Can not configure this PCM device: %s\n",snd_strerror(err));
        exit(1);
    }

    //设置多路数据在buffer中的存储方式
    //SND_PCM_ACCESS_RW_INTERLEAVED每个周期(period)左右声道的数据交叉存放
    err=snd_pcm_hw_params_set_access(handle, params,SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err < 0)
    {
        fprintf(stderr,"Failed to set PCM device to interleaved: %s\n",snd_strerror(err));
        exit(1);
    }

    //设置16位采样格式,S16为有符号16位,LE是小端模式
    err=snd_pcm_hw_params_set_format(handle, params,SND_PCM_FORMAT_S16_LE);
    if (err < 0)
    {
        fprintf(stderr,"Failed to set PCM device to 16-bit signed PCM: %s\n",snd_strerror(err));
        exit(1);
    }

    //设置声道数,双声道
    err=snd_pcm_hw_params_set_channels(handle, params, CHANNELS);
    if (err < 0)
    {
        fprintf(stderr, "Failed to set PCM device to mono: %s\n",snd_strerror(err));
        exit(1);
    }

    //采样率44100
    val = 44100;
    //设置采样率,如果采样率不支持,会用硬件支持最接近的采样率
    err=snd_pcm_hw_params_set_rate_near(handle, params,&val, &dir);
    if (err < 0)
    {
        fprintf(stderr, "Failed to set PCM device to sample rate =%d: %s\n",val,snd_strerror(err));
        exit(1);
    }

    unsigned int buffer_time,period_time;
    //获取最大的缓冲时间,buffer_time单位为us,500000us=0.5s
    snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
    if ( buffer_time >500000)
        buffer_time = 500000;

    //设置最大缓冲时间
    err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0);
    if (err < 0)
    {
        fprintf(stderr, "Failed to set PCM device to buffer time =%d: %s\n",buffer_time,snd_strerror(err));
        exit(1);
    }

    //采样时间,出现underrun可以加大
    period_time = 50000;
    //设置周期时间
    err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0);
    if (err < 0)
    {
        fprintf(stderr, "Failed to set PCM device to period time =%d: %s\n",period_time,snd_strerror(err));
        exit(1);
    }

    //让这些参数作用于PCM设备
    rc = snd_pcm_hw_params(handle, params);
    if (rc < 0)
    {
        fprintf(stderr,"unable to set hw parameters: %s\n",snd_strerror(rc));
        exit(1);
    }
    
    //采样点数
    snd_pcm_uframes_t frames;
    snd_pcm_hw_params_get_period_size(params, &frames,&dir);
    // 1 frame = channels * sample_size.

    char *buffer;
    int size = frames * FSIZE; /* 2 bytes/sample, 1 channels */

    buffer = (char *) malloc(size);

    //音频PCM帧
    AVFrame *pframePCM;
    pframePCM = av_frame_alloc();

    pframePCM->format = AV_SAMPLE_FMT_S16;
    pframePCM->channel_layout = AV_CH_LAYOUT_STEREO;
    pframePCM->sample_rate = 44100;
    pframePCM->nb_samples = frames;
    pframePCM->channels = CHANNELS;
    av_frame_get_buffer(pframePCM, 0);
    
    //音频ACC帧
    AVFrame *pframeAAC;
    pframeAAC = av_frame_alloc();

    int samples=0;
    
    printf("开始拉音视频流\n");

    while (1)
    {
        //获取包裹
        ret=av_read_frame(infmt_ctx,dec_pkt);
        if (ret != 0)
        {
            printf("fail to read_frame\n");
            break;
        }
        //判断视频包裹还是音频包裹
        if(dec_pkt->stream_index==videoindex)
        {

            //解包获取视频数据
            ret = avcodec_decode_video2(decodec_ctx, pFrame, &got_picture, dec_pkt);
            if(!got_picture)
            {
                printf("没有视频数据\n");
                continue;
            }

            //rgb24格式转换
            ret=sws_scale(rgb_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pFrameRGB->height, pFrameRGB->data, pFrameRGB->linesize);
            if (ret <= 0)
            {
                printf("rgb24格式转换失败\n");
                continue;
            }
            
            //这是qt里面信号操作
            //emit img_signal(pFrameRGB);
            
            //图片操作的话需要你们自己写。
        }
        else if(dec_pkt->stream_index==audioindex)
        {

            //解包获取音频数据
            ret = avcodec_decode_audio4(decodec_ctx2, pframeAAC, &got_picture, dec_pkt);
            if(!got_picture)
            {
                printf("没有音频帧\n");
                continue;
            }

            //AAC->PCM格式转换
            samples=swr_convert(pcm_convert_ctx,pframePCM->data, pframePCM->nb_samples,(const uint8_t **)pframeAAC->data, pframeAAC->nb_samples);
            if (ret <= 0)
            {
                printf("AAC->PCM格式转换失败\n");
                continue;
            }

            rc = snd_pcm_writei(handle, pframePCM->data[0], samples);
            if (rc == -EPIPE)
            {
                fprintf(stderr, "underrun occurred\n");
                err=snd_pcm_prepare(handle);
                if(err <0)
                {
                    fprintf(stderr, "can not recover from underrun: %s\n",snd_strerror(err));
                }
            }
            else if (rc < 0)
            {
                fprintf(stderr,"error from writei: %s\n",snd_strerror(rc));
            }
            else if (rc != (int)samples)
            {
                fprintf(stderr,"short write, write %d frames\n", rc);
            }
        }
        av_free_packet(dec_pkt);
    }

    avformat_close_input(&infmt_ctx);
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    free(buffer);
    printf("停止拉流\n");

}

3.拉流问题(暂时没有)

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值