ffmpeg+nginx搭建HLS服务器及基于ARM实现的简单hls解决方案

3 篇文章 0 订阅
1 篇文章 0 订阅

之前做的ffmpeg+ffserver实现http流媒体播放,现在做的是ffmpeg+nginx搭建HLS流媒体服务器。
由于做的是基于ARM上的,首先要做nginx的移植,ffmpeg移植网上很多可以参考,nginx的arm移植比较推荐这一篇,一步一步对照着来,没有问题http://www.tuicool.com/articles/QZVJjez
唯一要注意的是做rtmp式,nginx需要添加nginx-rtmp-module,在configure的时候需要增加nginx-rtmp-module的支持,下载好nginx-rtmp-module后解压,然后nginx安装时增加这个模块(–add-module),其它都是一样的.
ffmepg+hls

nginx的配置文件nginx.conf示例如下:


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;
    access_log off;
    error_log /dev/null crit; 

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /hls {  
            types {  
                application/vnd.apple.mpegurl m3u8;  
                video/mp2ts ts;  
            }  
           root /tmp;
        # root /home/root/main/bin/nginx/;  
            add_header Cache-Control no-cache;  
        }  


    #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }                                                                         
}
}

rtmp {

    server {  
           listen 1935;  
           application myapp {  
           live on;  
        }  
        application hls {  
            live on;  
            hls on;  
            hls_path /tmp/hls; 
        hls_fragment 2s;
        hls_playlist_length 4s;
        }  
    }  
    }                                                                           

主要包括了两个部分的配置信息,http和rtmp端口;
rtmp监听1935端口,application hls的存储路径是/tmp/hls,hls切片长度是2s。
( application myapp是做rtmp的)。

ffmpeg推送的rtmp地址是: rtmp://localhost:1935/hls/test2

nginx启动:nginx -c nginx.conf。

此次工作的不同在于ffmpeg需从FPGA fifo中读取数据,参考leixiaohua1020大神的ffmpeg内存读写,代码如下,主要修改了回调函数,修改了延时处理部分。

/**
  *
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#include "data_test.c"
#include <unistd.h>
#ifdef __cplusplus
};
#endif
#endif


#define QueueSize      32768*1024//*1024 //fifo队列的大小
#define QueueFull      0  //fifo满置0
#define QueueEmpty     1  //FIFO空置1
#define QueueOperateOk 2  //队列操作完成 赋值为2


struct FifoQueue
{
    unsigned int front;     //队列头
    unsigned int rear;        //队列尾
    unsigned int count;       //队列计数
    unsigned char dat[QueueSize];
};

struct FifoQueue MyQueue;
pthread_mutex_t Device_mutex;

//Queue Init
void QueueInit(struct FifoQueue *Queue)
{
    Queue->front = 0;
    Queue->rear =  0;//初始化时队列头队列首相连
    Queue->count = 0;   //队列计数为0
}
// Queue In
unsigned char QueueIn(struct FifoQueue *Queue,unsigned char sdat) //数据进入队列
{
    if((Queue->front == Queue->rear) && (Queue->count == QueueSize))
    {                    // full //判断如果队列满了
        return QueueFull;    //返回队列满的标志
    }else
    {                    // in
        Queue->dat[Queue->rear++] = sdat;   
    //printf("Queue elem:%d,%02x\n",Queue->rear,Queue->dat[Queue->rear]);       
    if(Queue->rear == QueueSize) Queue->rear = 0;
        Queue->count = Queue->count + 1;
        return QueueOperateOk;
    }
}
// Queue Out
unsigned char QueueOut(struct FifoQueue *Queue,unsigned char *sdat)
{
    if((Queue->front == Queue->rear) && (Queue->count == 0))
    {                    // empty
        return QueueEmpty;
    }
    else
    {                    // out
        *sdat = Queue->dat[Queue->front++]; 
    //printf("the queue:%d,%02x\n",Queue->front,Queue->dat[Queue->front]);        
    if(Queue->front == QueueSize) Queue->front = 0;
        Queue->count = Queue->count - 1;
        return QueueOperateOk;
    }
}


extern void* write_iobuffer(void *opaque)
{   
    int i;  
    unsigned char data_h,data_l;        
    unsigned short *len_addr,*data_addr;

    len_addr = (unsigned short*)(*(unsigned int*)opaque);
    data_addr = (unsigned short*)(*((unsigned int*)opaque+1));  

    while(1)
    {                       
        unsigned short len = *len_addr; 

        for (i = 0; i < len; i++)
        {               
            unsigned short data = *data_addr;
            data_h = (unsigned char)(data >> 8);
            data_l = (unsigned char)(data); 

            //pthread_mutex_lock(&Device_mutex);                            
            if(QueueIn(&MyQueue,data_h) == QueueFull)break;                         
            if(QueueIn(&MyQueue,data_l) == QueueFull)break;             
            //pthread_mutex_unlock(&Device_mutex);                  
        }
        //pthread_mutex_lock(&Device_mutex);
        usleep(1);
        //pthread_mutex_unlock(&Device_mutex);  

    }

}


int fill_iobuffer(void *opaque, unsigned char *buff, int buff_size)
{
    unsigned int totsize; 
    unsigned char sh;
    totsize = 0;

    //pthread_mutex_lock(&Device_mutex);        
    while(totsize < buff_size)
    {   
        pthread_mutex_lock(&Device_mutex);          
        if(QueueOut(&MyQueue,&sh) == QueueEmpty)break;      
        buff[totsize++] = sh;   
        pthread_mutex_unlock(&Device_mutex);        
    }
    //pthread_mutex_unlock(&Device_mutex);      

    usleep(100);        
    return totsize;
}



int main(int argc, char* argv[])
{
    FPGASet(argc,atoi(argv[1]),atoi(argv[2]),atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));    

    int fpga = atoi(argv[1]);       
    int fd;     
    unsigned short *addr[2];

    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
    {
        printf("open /dev/mem false!\n");
    }
    addr[0] = virt_map(fpga, fd, PIDFT_LEN);    
    addr[1] = virt_map(fpga, fd, PIDFT_DAT);


    AVOutputFormat *ofmt = NULL;    
    //Input AVFormatContext and Output AVFormatContext
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename; 
    int ret, i;
    int videoindex=-1;
    int frame_index=0;
    int64_t start_time=0;
    int flag = 1;


    //in_filename  = "cuc_ieschool.flv";//输入URL(Input file URL)

    //in_filename   = "btv2.ts";

    //out_filename = "rtmp://localhost/publishlive/livestream";//输出 URL(Output URL)[RTMP]   
    //out_filename = "udp://@224.2.2.33:8888?overrun_nonfatal=1&fifo_size=50000000";//输出 URL(Output URL)[UDP]   
    //out_filename = "http://localhost:8090/feed1.ffm";

    //out_filename = "gaoqing6.ts";
    //out_filename = "rtmp://localhost:1935/myapp/test1";
    out_filename = "rtmp://localhost:1935/hls/test2";//hls
    //out_filename = "rtmp://localhost:1935/myapp/test1";  //rtmp

    av_register_all();  
    avformat_network_init();
    QueueInit(&MyQueue);    //initial the buffer    


    ifmt_ctx = avformat_alloc_context();    
    unsigned char * iobuffer=(unsigned char *)av_malloc(32768);//32768

    pthread_t thread1;
    pthread_mutex_init(&Device_mutex,NULL);

    if(pthread_create(&thread1,NULL,write_iobuffer,addr) == -1){
        printf("create buf writer pthread error !\n");
        exit(1);
    }   
    sleep(5);   
    //write_iobuffer(addr); 


    //AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,fpga_dev,fill_iobuffer,NULL,NULL);
    AVIOContext *avio = avio_alloc_context(iobuffer,1024,0,0,fill_iobuffer,NULL,NULL);      
    ifmt_ctx->pb=avio;

    if ((ret = avformat_open_input(&ifmt_ctx, NULL, NULL, NULL)) < 0) {
        printf( "Could not open input file.\n");
        goto end;
    }       
    if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
        printf( "Failed to retrieve input stream information\n");
        goto end;
    }

    for(i=0; i<ifmt_ctx->nb_streams; i++)
        if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
        }
    av_dump_format(ifmt_ctx, 0, "nothing", 0);

    //Output

    //avformat_alloc_output_context2(&ofmt_ctx, NULL, "ffm", out_filename); //ffm
    avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
    //avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDP



    if (!ofmt_ctx) {
        printf( "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    ofmt = ofmt_ctx->oformat;
    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        //Create output AVStream according to input AVStream
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
        if (!out_stream) {
            printf( "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        //Copy the settings of AVCodecContext
        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            printf( "Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        out_stream->codec->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }
    //Dump Format------------------
    av_dump_format(ofmt_ctx, 0, out_filename, 1);
    //Open output URL
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            printf( "Could not open output URL '%s'", out_filename);
            goto end;
        }
    }
    //Write file header
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        printf( "Error occurred when opening output URL\n");
        goto end;
    }

    int64_t pts_start;
    //int64_t pts_front;
    //pts_front = 0;
    start_time=av_gettime();
    while (1) {
        //pthread_mutex_lock(&Device_mutex);            
        AVStream *in_stream, *out_stream;
        //Get an AVPacket

        ret = av_read_frame(ifmt_ctx, &pkt);        
        if (ret < 0)
            break;
        //FIX£ºNo PTS (Example: Raw H.264)
        //Simple Write PTS
    /*  if(pkt.pts==AV_NOPTS_VALUE){
            //Write PTS
            AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
            //Duration between 2 frames (us)
            int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);

            //Parameters            
            pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);          
            pkt.dts=pkt.pts-(double)(calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);          
            pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
        }
    */
        //Important:Delay
    /*  if(pkt.stream_index==videoindex){
            AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
            AVRational time_base_q={1,AV_TIME_BASE};
            int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
            int64_t now_time = av_gettime() - start_time;
            if (pts_time > now_time)
            {
                int64_t delay_time = (pts_time - now_time > 5000)?(5000):(pts_time - now_time);
                av_usleep(delay_time);
            }


        }
    */  
        //Important:Delay
        if(pkt.stream_index==videoindex){
            AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
            AVRational time_base_q={1,AV_TIME_BASE};
            while(flag){
            pts_start = av_rescale_q(pkt.dts, time_base, time_base_q);
            flag = 0;
            }           
            int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q)- pts_start;    
            int64_t now_time=av_gettime() - start_time;

            if (pts_time > now_time){
                int64_t delay_time = (pts_time - now_time > 40000)?(40000):(pts_time - now_time);
                av_usleep(delay_time);
            }       

        }       

        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
        /* copy packet */
        //Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        //Print to Screen
        if(pkt.stream_index==videoindex){
            printf("Send %8d video frames to output URL\n",frame_index);
            frame_index++;
        }

        //ret = av_write_frame(ofmt_ctx, &pkt);
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);


    /*  if (ret < 0) {
            printf( "Error muxing packet\n");
            //continue;//break   #modify by lyszhang
        }
    */  
        av_free_packet(&pkt);



    }
    //Write file trailer
    av_write_trailer(ofmt_ctx);

end:
    avformat_close_input(&ifmt_ctx);
    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    if (ret < 0 && ret != AVERROR_EOF) {
        printf( "Error occurred.\n");
        return -1;
    }
    return 0;
}

中间纠结了好几天回调函数的问题,在读取高清数字节目时,出现花屏卡顿的错误,百般修改,最终播放效果良好。
当我满心欢喜以为终于做成功时,却发现标清的数字电视节目均是mpeg2编码的,mpeg2没法用flv和mp4封装,rtmp没法传。这怎么办,做转码?arm根本跑不了转码。那只能自己做切片,一种方法就是前面程序推udp的流,再启一个ffmpeg做切片,这样的话,ffmpeg前面解析时间占用大约15s的时间,而且完整的ffmpeg也不好控制,整体架构混乱。另一种就是分析ffmpeg切片api,重写基于ffmpeg的内存读写切片,可以研究,但是没时间,后续一周再做。最投机取巧就是,前面FIFO读数据时,每隔几秒保存一段,做简单的切片工作,自己去写playlist.m3u8。ffmpeg在做切片时,插入了很多信息,以保证播放正常。这种简单切片可能有问题,但是从播放结果上来说,感觉良好。
大体代码如下,切为5段,每段3s,循环读写,1分钟超时自动关闭。

```
int write_iobuffer(int fpga,int fp_time)
{   
    int i;  
    int fp_count;   
    unsigned short *len_addr,*data_addr;
    int totSize;
    unsigned char data_buf[BUF_SIZE];
    double tpstart,tpend;
    double duration; 
    double time_temp;
    timeval tv; 
    int cycle_count;

    cycle_count = 0;
    totSize = 0;
    fp_count = 0;

    int fd;         
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
    {
        printf("open /dev/mem false!\n");
        return -1;
    }   
    len_addr = virt_map(fpga, fd, PIDFT_LEN);   
    data_addr = virt_map(fpga, fd, PIDFT_DAT);


    FILE *fp1,*fp2,*fp3,*fp4,*fp5;
    if(fp1=fopen("/home/root/main/bin/nginx/hls/output0000.ts","wb+")) 
        printf("open file successful!");
    else
        return -1;

    if(fp2=fopen("/home/root/main/bin/nginx/hls/output0001.ts","wb+")) 
        printf("open file successful!");
    else
        return -1;

    if(fp3=fopen("/home/root/main/bin/nginx/hls/output0002.ts","wb+")) 
        printf("open file successful!");
    else
        return -1;
    if(fp4=fopen("//home/root/main/bin/nginx/hls/output0003.ts","wb+"))         printf("open file successful!");
    else
        return -1;

    if(fp5=fopen("/home/root/main/bin/nginx/hls/output0004.ts","wb+")) 
        printf("open file successful!");
    else
        return -1;

    FILE *list_fp;
    if(list_fp = fopen("/home/root/main/bin/nginx/hls/playlist.m3u8","wt+")) 
        printf("open file successful!");
    else
        return -1;  

    fputs("#EXTM3U\n",list_fp);
    fputs("#EXT-X-VERSION:3\n",list_fp);
    fputs("#EXT-X-MEDIA-SEQUENCE:0\n",list_fp);
    fputs("#EXT-X-ALLOW-CACHE:YES\n",list_fp);
    fputs("#EXT-X-TARGETDURATION:4\n",list_fp); 

    gettimeofday(&tv,NULL);
    time_temp = tv.tv_usec;
    tpstart = tv.tv_sec + time_temp/1000000;

    while(1)
    {                       
        unsigned short len = *len_addr;         
        printf("data length:%d\n",len);

        for (i = 0; i < len; i++)
        {               
            unsigned short data = *data_addr;
            data_buf[totSize++] = (unsigned char)(data >> 8);
            data_buf[totSize++] = (unsigned char)(data);
        }       
        usleep(1000);

        gettimeofday(&tv,NULL);
        time_temp = tv.tv_usec;
        tpend = tv.tv_sec + time_temp/1000000;
        duration = tpend - tpstart;

        if(duration > 3)
        {
            switch(fp_count)
            {           
                case 0:
                {
                    ftruncate(fileno(fp1),0);
                    fseek(fp1,0L,SEEK_SET);                 
                    fwrite(data_buf, sizeof(unsigned char), totSize, fp1);                  

                    fseek(list_fp,0L,SEEK_END);                 
                    fprintf(list_fp,"#EXTINF:%f,\n",duration);
                    fputs("output0000.ts\n",list_fp);                   

                    fp_count++;                             
                    totSize = 0;
                    tpstart = tpend;                    
                    break;                  
                }

                case 1:
                {
                    ftruncate(fileno(fp2),0);                   
                    fseek(fp2,0L,SEEK_SET);
                    fwrite(data_buf, sizeof(unsigned char), totSize, fp2);

                    fseek(list_fp,0L,SEEK_END);
                    fprintf(list_fp,"#EXTINF:%f,\n",duration);
                    fputs("output0001.ts\n",list_fp);                   

                    fp_count++;                             
                    totSize = 0;
                    tpstart = tpend;
                    play_stat = 1;                  
                    break;                  
                }

                case 2:
                {
                    ftruncate(fileno(fp3),0);                   
                    fseek(fp3,0L,SEEK_SET);                 
                    fwrite(data_buf, sizeof(unsigned char), totSize, fp3);

                    fseek(list_fp,0L,SEEK_END);
                    fprintf(list_fp,"#EXTINF:%f,\n",duration);
                    fputs("output0002.ts\n",list_fp);                   

                    fp_count++;                             
                    totSize = 0;
                    tpstart = tpend;                    
                    break;                  
                }

                case 3:
                {
                    ftruncate(fileno(fp4),0);
                    fseek(fp4,0L,SEEK_SET);                 
                    fwrite(data_buf, sizeof(unsigned char), totSize, fp4);

                    fseek(list_fp,0L,SEEK_END);
                    fprintf(list_fp,"#EXTINF:%f,\n",duration);
                    fputs("output0003.ts\n",list_fp);                   

                    fp_count++;                             
                    totSize = 0;
                    tpstart = tpend;
                    break;                  
                }                       

                case 4:
                {
                    ftruncate(fileno(fp5),0);                   
                    fseek(fp5,0L,SEEK_SET);         
                    fwrite(data_buf, sizeof(unsigned char), totSize, fp5);


                    fseek(list_fp,0L,SEEK_END);
                    fprintf(list_fp,"#EXTINF:%f,\n",duration);
                    fputs("output0004.ts\n",list_fp);                   
                    fp_count = 0;                               
                    totSize = 0;
                    tpstart = tpend;
                    break;                  
                }
            }
            cycle_count++;  
            if(cycle_count == 20)break; 

        }
        if(pthread_stat == 0 || fp_count == 5)
            break;  
    }
    close(fd);
    fclose(fp1);
    fclose(fp2);
    fclose(fp3);
    fclose(fp4);
    fclose(fp5);
    fclose(list_fp);
    return 0;   

}

playlist如下:

playlist.m3u8

注意X-TARGETDURATION需大于切皮时间,否则片与片之间播放时会有空白。
切片位置是nginx的hls的读取目录。
这里写图片描述
ok!
延时为2段的时间即6s,追求更小的延时可以缩小切片时间,或者前两段切片时间缩小。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值