rtsp server ok
git-svn-id: http://192.168.1.226/svn/proxy@34 454eff88-639b-444f-9e54-f578c98de674
New file |
| | |
| | | // |
| | | // FFmpegDecoder.cpp |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #include "FFmpegDecoder.h" |
| | | |
| | | namespace MESAI |
| | | { |
| | | FFmpegDecoder::FFmpegDecoder(std::string path) |
| | | { |
| | | this->path = path; |
| | | } |
| | | |
| | | |
| | | void FFmpegDecoder::intialize() |
| | | { |
| | | |
| | | // Intialize FFmpeg enviroment |
| | | av_register_all(); |
| | | avdevice_register_all(); |
| | | avcodec_register_all(); |
| | | avformat_network_init(); |
| | | |
| | | const char *filenameSrc = path.c_str(); |
| | | |
| | | pFormatCtx = avformat_alloc_context(); |
| | | |
| | | AVCodec * pCodec; |
| | | |
| | | if(avformat_open_input(&pFormatCtx,filenameSrc,NULL,NULL) != 0) |
| | | { |
| | | //exception |
| | | return; |
| | | } |
| | | |
| | | if(avformat_find_stream_info(pFormatCtx, NULL) < 0) |
| | | { |
| | | //exception |
| | | return; |
| | | } |
| | | |
| | | av_dump_format(pFormatCtx, 0, filenameSrc, 0); |
| | | |
| | | videoStream = -1; |
| | | |
| | | for(int i=0; i < pFormatCtx->nb_streams; i++) |
| | | { |
| | | AVStream *st = pFormatCtx->streams[i]; |
| | | enum AVMediaType type = st->codec->codec_type; |
| | | if (videoStream == -1) |
| | | if (avformat_match_stream_specifier(pFormatCtx, st, "vst") > 0) |
| | | videoStream = i; |
| | | } |
| | | |
| | | videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO,videoStream, -1, NULL, 0); |
| | | |
| | | if(videoStream == -1) |
| | | { |
| | | //exception |
| | | return; |
| | | } |
| | | |
| | | pCodecCtx = pFormatCtx->streams[videoStream]->codec; |
| | | |
| | | pCodec =avcodec_find_decoder(pCodecCtx->codec_id); |
| | | if(pCodec==NULL) |
| | | { |
| | | //exception |
| | | return; |
| | | } |
| | | |
| | | pCodecCtx->codec_id = pCodec->id; |
| | | pCodecCtx->workaround_bugs = 1; |
| | | |
| | | if(avcodec_open2(pCodecCtx,pCodec,NULL) < 0) |
| | | { |
| | | //exception |
| | | return; |
| | | } |
| | | |
| | | pFrameRGB = av_frame_alloc(); |
| | | AVPixelFormat pFormat = AV_PIX_FMT_BGR24; |
| | | uint8_t *fbuffer; |
| | | int numBytes; |
| | | numBytes = avpicture_get_size(pFormat,pCodecCtx->width,pCodecCtx->height) ; //AV_PIX_FMT_RGB24 |
| | | fbuffer = (uint8_t *) av_malloc(numBytes*sizeof(uint8_t)); |
| | | avpicture_fill((AVPicture *) pFrameRGB,fbuffer,pFormat,pCodecCtx->width,pCodecCtx->height); |
| | | |
| | | img_convert_ctx = sws_getCachedContext(NULL,pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL,NULL); |
| | | |
| | | height = pCodecCtx->height; |
| | | width = pCodecCtx->width; |
| | | bitrate =pCodecCtx->bit_rate; |
| | | GOP = pCodecCtx->gop_size; |
| | | frameRate = (int )pFormatCtx->streams[videoStream]->avg_frame_rate.num/pFormatCtx->streams[videoStream]->avg_frame_rate.den; |
| | | |
| | | |
| | | } |
| | | |
| | | void FFmpegDecoder::setOnframeCallbackFunction(std::function<void(uint8_t *)> func) |
| | | { |
| | | onFrame = func; |
| | | } |
| | | |
| | | |
| | | void FFmpegDecoder::playMedia() |
| | | { |
| | | AVPacket packet; |
| | | AVFrame * pFrame; |
| | | while((av_read_frame(pFormatCtx,&packet)>=0)) |
| | | { |
| | | if(packet.buf != NULL & packet.stream_index == videoStream) |
| | | { |
| | | pFrame = av_frame_alloc(); |
| | | int frameFinished; |
| | | int decode_ret = avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,&packet); |
| | | av_free_packet(&packet); |
| | | if(frameFinished) |
| | | { |
| | | sws_scale(img_convert_ctx, ((AVPicture*)pFrame)->data, ((AVPicture*)pFrame)->linesize, 0, pCodecCtx->height, ((AVPicture *)pFrameRGB)->data, ((AVPicture *)pFrameRGB)->linesize); |
| | | onFrame(((AVPicture *)pFrameRGB)->data[0]); |
| | | } |
| | | av_frame_unref(pFrame); |
| | | av_free(pFrame); |
| | | } |
| | | usleep(((double)(1.0/frameRate))*1000000); |
| | | |
| | | } |
| | | av_free_packet(&packet); |
| | | |
| | | |
| | | } |
| | | |
| | | void FFmpegDecoder::finalize() |
| | | { |
| | | sws_freeContext(img_convert_ctx); |
| | | av_freep(&(pFrameRGB->data[0])); |
| | | av_frame_unref(pFrameRGB); |
| | | av_free(pFrameRGB); |
| | | avcodec_close(pCodecCtx); |
| | | avformat_close_input(&pFormatCtx); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | // |
| | | // FFmpegDecoder.h |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #ifndef MESAI_FFmpegDecoder_H |
| | | #define MESAI_FFmpegDecoder_H |
| | | |
| | | #include <iostream> |
| | | #include <string> |
| | | #include <pthread.h> |
| | | #include <functional> |
| | | #include <unistd.h> |
| | | |
| | | extern "C" { |
| | | #include <libavutil/mathematics.h> |
| | | #include <libavutil/imgutils.h> |
| | | #include <libswscale/swscale.h> |
| | | #include <libavutil/pixdesc.h> |
| | | #include <libavdevice/avdevice.h> |
| | | } |
| | | |
| | | |
| | | |
| | | namespace MESAI |
| | | { |
| | | class FFmpegDecoder |
| | | { |
| | | public: |
| | | FFmpegDecoder(std::string); |
| | | |
| | | ~FFmpegDecoder(); |
| | | |
| | | void intialize(); |
| | | |
| | | void playMedia(); |
| | | |
| | | void finalize(); |
| | | |
| | | void setOnframeCallbackFunction(std::function<void(uint8_t *)> func); |
| | | |
| | | int width; |
| | | |
| | | int height; |
| | | |
| | | int GOP; |
| | | |
| | | int frameRate; |
| | | |
| | | int bitrate; |
| | | |
| | | std::function<void(uint8_t *)> onFrame; |
| | | |
| | | private: |
| | | |
| | | std::string path; |
| | | |
| | | AVCodecContext *pCodecCtx; |
| | | |
| | | AVFormatContext *pFormatCtx; |
| | | |
| | | AVFrame *pFrameRGB; |
| | | |
| | | struct SwsContext * img_convert_ctx; |
| | | |
| | | int videoStream; |
| | | |
| | | |
| | | }; |
| | | |
| | | |
| | | } |
| | | |
| | | #endif |
New file |
| | |
| | | // |
| | | // FFmpegH264Encoder.cpp |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #include "FFmpegH264Encoder.h" |
| | | |
| | | namespace MESAI |
| | | { |
| | | FFmpegH264Encoder::FFmpegH264Encoder() |
| | | { |
| | | pthread_mutex_init(&inqueue_mutex,NULL); |
| | | pthread_mutex_init(&outqueue_mutex,NULL); |
| | | |
| | | } |
| | | |
| | | void FFmpegH264Encoder::setCallbackFunctionFrameIsReady(std::function<void()> func) |
| | | { |
| | | onFrame = func; |
| | | } |
| | | |
| | | |
| | | void FFmpegH264Encoder::SendNewFrame(uint8_t * RGBFrame) { |
| | | pthread_mutex_lock(&inqueue_mutex); |
| | | if(inqueue.size()<30) |
| | | { |
| | | inqueue.push(RGBFrame); |
| | | } |
| | | pthread_mutex_unlock(&inqueue_mutex); |
| | | } |
| | | |
| | | void FFmpegH264Encoder::run() |
| | | { |
| | | while(true) |
| | | { |
| | | if(!inqueue.empty()) |
| | | { |
| | | uint8_t * frame; |
| | | pthread_mutex_lock(&inqueue_mutex); |
| | | frame = inqueue.front(); |
| | | inqueue.pop(); |
| | | pthread_mutex_unlock(&inqueue_mutex); |
| | | if(frame != NULL) |
| | | { |
| | | WriteFrame(frame); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | void FFmpegH264Encoder::SetupCodec(const char *filename, int codec_id) |
| | | { |
| | | int ret; |
| | | m_sws_flags = SWS_BICUBIC; |
| | | m_frame_count=0; |
| | | |
| | | avcodec_register_all(); |
| | | av_register_all(); |
| | | |
| | | avformat_alloc_output_context2(&m_oc, NULL, NULL, filename); |
| | | |
| | | if (!m_oc) { |
| | | avformat_alloc_output_context2(&m_oc, NULL, "avi", filename); |
| | | } |
| | | |
| | | if (!m_oc) { |
| | | return; |
| | | } |
| | | |
| | | m_fmt = m_oc->oformat; |
| | | m_video_st = NULL; |
| | | m_fmt->video_codec = (AVCodecID)codec_id; |
| | | m_fmt->audio_codec = AV_CODEC_ID_NONE; |
| | | |
| | | AVStream *st; |
| | | |
| | | m_video_codec = avcodec_find_encoder(m_fmt->video_codec); |
| | | if (!(m_video_codec)) { |
| | | return; |
| | | } |
| | | |
| | | st = avformat_new_stream(m_oc, m_video_codec); |
| | | |
| | | if (!st) { |
| | | return; |
| | | } |
| | | |
| | | st->id = m_oc->nb_streams-1; |
| | | |
| | | m_c = st->codec; |
| | | |
| | | m_c->codec_id = m_fmt->video_codec; |
| | | m_c->bit_rate = m_AVIMOV_BPS; //Bits Per Second |
| | | m_c->width = m_AVIMOV_WIDTH; //Note Resolution must be a multiple of 2!! |
| | | m_c->height = m_AVIMOV_HEIGHT; //Note Resolution must be a multiple of 2!! |
| | | m_c->time_base.den = m_AVIMOV_FPS; //Frames per second |
| | | m_c->time_base.num = 1; |
| | | m_c->gop_size = m_AVIMOV_GOB; // Intra frames per x P frames |
| | | m_c->pix_fmt = AV_PIX_FMT_YUV420P;//Do not change this, H264 needs YUV format not RGB |
| | | |
| | | |
| | | if (m_oc->oformat->flags & AVFMT_GLOBALHEADER) |
| | | m_c->flags |= CODEC_FLAG_GLOBAL_HEADER; |
| | | |
| | | m_video_st=st; |
| | | |
| | | |
| | | AVCodecContext *c = m_video_st->codec; |
| | | |
| | | ret = avcodec_open2(c, m_video_codec, NULL); |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | |
| | | //ret = avpicture_alloc(&m_dst_picture, c->pix_fmt, c->width, c->height); |
| | | m_dst_picture = av_frame_alloc(); |
| | | m_dst_picture->format = c->pix_fmt; |
| | | m_dst_picture->data[0] = NULL; |
| | | m_dst_picture->linesize[0] = -1; |
| | | m_dst_picture->pts = 0; |
| | | m_dst_picture->width = m_c->width; |
| | | m_dst_picture->height = m_c->height; |
| | | |
| | | ret = av_image_alloc(m_dst_picture->data, m_dst_picture->linesize, c->width, c->height, (AVPixelFormat)m_dst_picture->format, 32); |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | |
| | | //ret = avpicture_alloc(&m_src_picture, AV_PIX_FMT_BGR24, c->width, c->height); |
| | | m_src_picture = av_frame_alloc(); |
| | | m_src_picture->format = c->pix_fmt; |
| | | ret = av_image_alloc(m_src_picture->data, m_src_picture->linesize, c->width, c->height, AV_PIX_FMT_BGR24, 24); |
| | | |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | |
| | | bufferSize = ret; |
| | | |
| | | av_dump_format(m_oc, 0, filename, 1); |
| | | |
| | | if (!(m_fmt->flags & AVFMT_NOFILE)) { |
| | | ret = avio_open(&m_oc->pb, filename, AVIO_FLAG_WRITE); |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | ret = avformat_write_header(m_oc, NULL); |
| | | |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | |
| | | sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_BGR24, |
| | | c->width, c->height, AV_PIX_FMT_YUV420P, |
| | | SWS_BICUBIC, NULL, NULL, NULL); |
| | | if (!sws_ctx) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | void FFmpegH264Encoder::WriteFrame(uint8_t * RGBFrame ) |
| | | { |
| | | |
| | | memcpy(m_src_picture->data[0], RGBFrame, bufferSize); |
| | | |
| | | sws_scale(sws_ctx, |
| | | m_src_picture->data, m_src_picture->linesize, |
| | | 0, m_c->height, m_dst_picture->data, m_dst_picture->linesize); |
| | | |
| | | |
| | | AVPacket pkt = { 0 }; |
| | | int got_packet; |
| | | av_init_packet(&pkt); |
| | | |
| | | int ret = 0; |
| | | |
| | | ret = avcodec_encode_video2(m_c, &pkt, m_dst_picture, &got_packet); |
| | | |
| | | if (ret < 0) { |
| | | return; |
| | | } |
| | | |
| | | if (!ret && got_packet && pkt.size) |
| | | { |
| | | pkt.stream_index = m_video_st->index; |
| | | FrameStructure * frame = new FrameStructure(); |
| | | frame->dataPointer = new uint8_t[pkt.size]; |
| | | frame->dataSize = pkt.size-4; |
| | | frame->frameID = m_frame_count; |
| | | |
| | | memcpy(frame->dataPointer,pkt.data+4,pkt.size-4); |
| | | |
| | | pthread_mutex_lock(&outqueue_mutex); |
| | | |
| | | if(outqueue.size()<30) |
| | | { |
| | | outqueue.push(frame); |
| | | } |
| | | else |
| | | { |
| | | delete frame; |
| | | } |
| | | |
| | | pthread_mutex_unlock(&outqueue_mutex); |
| | | |
| | | } |
| | | |
| | | av_free_packet(&pkt); |
| | | |
| | | m_frame_count++; |
| | | m_dst_picture->pts += av_rescale_q(1, m_video_st->codec->time_base, m_video_st->time_base); |
| | | |
| | | onFrame(); |
| | | } |
| | | |
| | | void FFmpegH264Encoder::SetupVideo(std::string filename, int Width, int Height, int FPS, int GOB, int BitPerSecond) |
| | | { |
| | | m_filename = filename; |
| | | m_AVIMOV_WIDTH=Width; //Movie width |
| | | m_AVIMOV_HEIGHT=Height; //Movie height |
| | | m_AVIMOV_FPS=FPS; //Movie frames per second |
| | | m_AVIMOV_GOB=GOB; //I frames per no of P frames, see note below! |
| | | m_AVIMOV_BPS=BitPerSecond; //Bits per second, if this is too low then movie will become garbled |
| | | |
| | | SetupCodec(m_filename.c_str(),AV_CODEC_ID_H264); |
| | | } |
| | | |
| | | void FFmpegH264Encoder::CloseCodec() |
| | | { |
| | | |
| | | av_write_trailer(m_oc); |
| | | avcodec_close(m_video_st->codec); |
| | | |
| | | av_freep(&(m_dst_picture->data[0])); |
| | | av_frame_unref(m_dst_picture); |
| | | av_free(m_dst_picture); |
| | | av_freep(&(m_src_picture->data[0])); |
| | | av_frame_unref(m_src_picture); |
| | | av_free(m_src_picture); |
| | | |
| | | if (!(m_fmt->flags & AVFMT_NOFILE)) |
| | | avio_close(m_oc->pb); |
| | | |
| | | m_oc->pb = NULL; |
| | | |
| | | avformat_free_context(m_oc); |
| | | sws_freeContext(sws_ctx); |
| | | |
| | | } |
| | | |
| | | void FFmpegH264Encoder::CloseVideo() |
| | | { |
| | | CloseCodec(); |
| | | } |
| | | |
| | | char FFmpegH264Encoder::GetFrame(u_int8_t** FrameBuffer, unsigned int *FrameSize) |
| | | { |
| | | if(!outqueue.empty()) |
| | | { |
| | | FrameStructure * frame; |
| | | frame = outqueue.front(); |
| | | *FrameBuffer = (uint8_t*)frame->dataPointer; |
| | | *FrameSize = frame->dataSize; |
| | | return 1; |
| | | } |
| | | else |
| | | { |
| | | *FrameBuffer = 0; |
| | | *FrameSize = 0; |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | char FFmpegH264Encoder::ReleaseFrame() |
| | | { |
| | | pthread_mutex_lock(&outqueue_mutex); |
| | | if(!outqueue.empty()) |
| | | { |
| | | FrameStructure * frame = outqueue.front(); |
| | | outqueue.pop(); |
| | | delete frame; |
| | | } |
| | | pthread_mutex_unlock(&outqueue_mutex); |
| | | return 1; |
| | | } |
| | | } |
New file |
| | |
| | | // |
| | | // FFmpegH264Encoder.h |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #ifndef MESAI_FFMPEGH264_ENCODER_H |
| | | #define MESAI_FFMPEGH264_ENCODER_H |
| | | |
| | | #include <string> |
| | | #include <queue> |
| | | #include <pthread.h> |
| | | #include <functional> |
| | | |
| | | #include "IEncoder.h" |
| | | |
| | | extern "C" { |
| | | |
| | | #include <stdlib.h> |
| | | #include <stdio.h> |
| | | #include <string.h> |
| | | #include <math.h> |
| | | #include <libavutil/opt.h> |
| | | #include <libavutil/mathematics.h> |
| | | #include <libavformat/avformat.h> |
| | | #include <libswscale/swscale.h> |
| | | #include <libswresample/swresample.h> |
| | | #include <libavutil/imgutils.h> |
| | | #include <libavcodec/avcodec.h> |
| | | |
| | | } |
| | | |
| | | namespace MESAI |
| | | { |
| | | class FrameStructure { |
| | | public: |
| | | uint8_t * dataPointer; |
| | | int dataSize; |
| | | int frameID; |
| | | ~FrameStructure() |
| | | { |
| | | delete dataPointer; |
| | | } |
| | | }; |
| | | |
| | | class FFmpegH264Encoder : public IEncoder |
| | | { |
| | | public: |
| | | FFmpegH264Encoder(); |
| | | ~FFmpegH264Encoder(); |
| | | |
| | | virtual void setCallbackFunctionFrameIsReady(std::function<void()> func); |
| | | |
| | | void SetupVideo(std::string filename, int Width, int Height, int FPS, int GOB, int BitPerSecond); |
| | | void CloseVideo(); |
| | | void SetupCodec(const char *filename, int codec_id); |
| | | void CloseCodec(); |
| | | |
| | | |
| | | void SendNewFrame(uint8_t * RGBFrame); |
| | | void WriteFrame(uint8_t * RGBFrame); |
| | | virtual char ReleaseFrame(); |
| | | |
| | | void run(); |
| | | virtual char GetFrame(u_int8_t** FrameBuffer, unsigned int *FrameSize); |
| | | |
| | | private: |
| | | |
| | | |
| | | std::queue<uint8_t*> inqueue; |
| | | pthread_mutex_t inqueue_mutex; |
| | | std::queue<FrameStructure *> outqueue; |
| | | pthread_mutex_t outqueue_mutex; |
| | | |
| | | |
| | | int m_sws_flags; |
| | | int m_AVIMOV_FPS; |
| | | int m_AVIMOV_GOB; |
| | | int m_AVIMOV_BPS; |
| | | int m_frame_count; |
| | | int m_AVIMOV_WIDTH; |
| | | int m_AVIMOV_HEIGHT; |
| | | std::string m_filename; |
| | | |
| | | double m_video_time; |
| | | |
| | | AVCodecContext *m_c; |
| | | AVStream *m_video_st; |
| | | AVOutputFormat *m_fmt; |
| | | AVFormatContext *m_oc; |
| | | AVCodec *m_video_codec; |
| | | AVFrame * m_src_picture, * m_dst_picture; |
| | | SwsContext *sws_ctx; |
| | | int bufferSize; |
| | | |
| | | std::function<void()> onFrame; |
| | | |
| | | }; |
| | | } |
| | | #endif |
New file |
| | |
| | | //
|
| | | // FFmpegH264Source.cpp
|
| | | // FFmpegRTSPServer
|
| | | //
|
| | | // Created by Mina Saad on 9/22/15.
|
| | | // Copyright (c) 2015 Mina Saad. All rights reserved.
|
| | | //
|
| | |
|
| | | #include "FFmpegH264Source.h"
|
| | |
|
| | | namespace MESAI
|
| | | {
|
| | | FFmpegH264Source * FFmpegH264Source::createNew(UsageEnvironment& env, IEncoder * E_Source) {
|
| | | return new FFmpegH264Source(env, E_Source);
|
| | | }
|
| | |
|
| | | FFmpegH264Source::FFmpegH264Source(UsageEnvironment& env, IEncoder * E_Source) : FramedSource(env), Encoding_Source(E_Source)
|
| | | {
|
| | | m_eventTriggerId = envir().taskScheduler().createEventTrigger(FFmpegH264Source::deliverFrameStub);
|
| | | std::function<void()> callback1 = std::bind(&FFmpegH264Source::onFrame,this);
|
| | | Encoding_Source -> setCallbackFunctionFrameIsReady(callback1);
|
| | | }
|
| | |
|
| | | FFmpegH264Source::~FFmpegH264Source()
|
| | | {
|
| | |
|
| | | }
|
| | |
|
| | | void FFmpegH264Source::doStopGettingFrames()
|
| | | {
|
| | | FramedSource::doStopGettingFrames();
|
| | | }
|
| | |
|
| | | void FFmpegH264Source::onFrame()
|
| | | {
|
| | | envir().taskScheduler().triggerEvent(m_eventTriggerId, this);
|
| | | }
|
| | |
|
| | | void FFmpegH264Source::doGetNextFrame()
|
| | | {
|
| | | deliverFrame();
|
| | | }
|
| | |
|
| | | void FFmpegH264Source::deliverFrame()
|
| | | {
|
| | | if (!isCurrentlyAwaitingData()) return; // we're not ready for the data yet
|
| | |
|
| | | static uint8_t* newFrameDataStart;
|
| | | static unsigned newFrameSize = 0;
|
| | |
|
| | | /* get the data frame from the Encoding thread.. */
|
| | | if (Encoding_Source->GetFrame(&newFrameDataStart, &newFrameSize)){
|
| | | if (newFrameDataStart!=NULL) {
|
| | | /* This should never happen, but check anyway.. */
|
| | | if (newFrameSize > fMaxSize) {
|
| | | fFrameSize = fMaxSize;
|
| | | fNumTruncatedBytes = newFrameSize - fMaxSize;
|
| | | } else {
|
| | | fFrameSize = newFrameSize;
|
| | | }
|
| | |
|
| | | gettimeofday(&fPresentationTime, NULL);
|
| | | memcpy(fTo, newFrameDataStart, fFrameSize);
|
| | | |
| | | //delete newFrameDataStart;
|
| | | //newFrameSize = 0;
|
| | | |
| | | Encoding_Source->ReleaseFrame();
|
| | | }
|
| | | else {
|
| | | fFrameSize=0;
|
| | | fTo=NULL;
|
| | | handleClosure(this);
|
| | | }
|
| | | }else
|
| | | {
|
| | | fFrameSize = 0;
|
| | | }
|
| | | |
| | | if(fFrameSize>0)
|
| | | FramedSource::afterGetting(this);
|
| | |
|
| | | }
|
| | | } |
New file |
| | |
| | | // |
| | | // FFmpegH264Source.h |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #ifndef MESAI_FFMPEGH264_SOURCE_HH |
| | | #define MESAI_FFMPEGH264_SOURCE_HH |
| | | |
| | | |
| | | #include <functional> |
| | | #include <FramedSource.hh> |
| | | #include <UsageEnvironment.hh> |
| | | #include <Groupsock.hh> |
| | | #include "IEncoder.h" |
| | | |
| | | namespace MESAI |
| | | { |
| | | |
| | | class FFmpegH264Source : public FramedSource { |
| | | public: |
| | | static FFmpegH264Source* createNew(UsageEnvironment& env, IEncoder * E_Source); |
| | | FFmpegH264Source(UsageEnvironment& env, IEncoder * E_Source); |
| | | ~FFmpegH264Source(); |
| | | |
| | | private: |
| | | static void deliverFrameStub(void* clientData) {((FFmpegH264Source*) clientData)->deliverFrame();}; |
| | | virtual void doGetNextFrame(); |
| | | void deliverFrame(); |
| | | virtual void doStopGettingFrames(); |
| | | void onFrame(); |
| | | |
| | | |
| | | private: |
| | | IEncoder * Encoding_Source; |
| | | EventTriggerId m_eventTriggerId; |
| | | |
| | | }; |
| | | |
| | | } |
| | | #endif |
New file |
| | |
| | | #ifndef MESAI_I_ENCODER_H
|
| | | #define MESAI_I_ENCODER_H
|
| | |
|
| | | #include <functional>
|
| | |
|
| | | namespace MESAI
|
| | | {
|
| | | class IEncoder
|
| | | {
|
| | | public:
|
| | | IEncoder() { }
|
| | | virtual ~IEncoder() { }
|
| | | virtual void setCallbackFunctionFrameIsReady(std::function<void()> func) = 0;
|
| | | virtual char GetFrame(u_int8_t** FrameBuffer, unsigned int *FrameSize) = 0;
|
| | | virtual char ReleaseFrame() = 0;
|
| | | };
|
| | | }
|
| | |
|
| | | #endif
|
New file |
| | |
| | | //
|
| | | // LiveRTSPServer.cpp
|
| | | // FFmpegRTSPServer
|
| | | //
|
| | | // Created by Mina Saad on 9/22/15.
|
| | | // Copyright (c) 2015 Mina Saad. All rights reserved.
|
| | | //
|
| | |
|
| | | #include "LiveRTSPServer.h"
|
| | |
|
| | | namespace MESAI
|
| | | {
|
| | | LiveRTSPServer::LiveRTSPServer( IEncoder * a_Encoder, int port, int httpPort )
|
| | | : m_Encoder (a_Encoder), portNumber(port), httpTunnelingPort(httpPort)
|
| | | {
|
| | | quit = 0;
|
| | | }
|
| | |
|
| | | LiveRTSPServer::~LiveRTSPServer()
|
| | | {
|
| | |
|
| | | }
|
| | |
|
| | | void LiveRTSPServer::run()
|
| | | {
|
| | | TaskScheduler *scheduler;
|
| | | UsageEnvironment *env ;
|
| | | char RTSP_Address[1024];
|
| | | RTSP_Address[0]=0x00;
|
| | |
|
| | | scheduler = BasicTaskScheduler::createNew();
|
| | | env = BasicUsageEnvironment::createNew(*scheduler);
|
| | | |
| | | UserAuthenticationDatabase* authDB = NULL;
|
| | | |
| | | // if (m_Enable_Pass){
|
| | | // authDB = new UserAuthenticationDatabase;
|
| | | // authDB->addUserRecord(UserN, PassW);
|
| | | // }
|
| | | |
| | | OutPacketBuffer::maxSize = 2000000;
|
| | | RTSPServer* rtspServer = RTSPServer::createNew(*env, portNumber, authDB);
|
| | | |
| | | if (rtspServer == NULL)
|
| | | {
|
| | | *env <<"LIVE555: Failed to create RTSP server: %s\n", env->getResultMsg();
|
| | | }
|
| | | else {
|
| | | |
| | | |
| | | if(httpTunnelingPort)
|
| | | {
|
| | | rtspServer->setUpTunnelingOverHTTP(httpTunnelingPort);
|
| | | }
|
| | | |
| | | char const* descriptionString = "MESAI Streaming Session";
|
| | | |
| | | FFmpegH264Source * source = FFmpegH264Source::createNew(*env,m_Encoder);
|
| | | StreamReplicator * inputDevice = StreamReplicator::createNew(*env, source, false);
|
| | | |
| | | ServerMediaSession* sms = ServerMediaSession::createNew(*env, RTSP_Address, RTSP_Address, descriptionString);
|
| | | sms->addSubsession(MESAI::LiveServerMediaSubsession::createNew(*env, inputDevice));
|
| | | rtspServer->addServerMediaSession(sms);
|
| | | |
| | | char* url = rtspServer->rtspURL(sms);
|
| | | *env << "Play this stream using the URL \"" << url << "\"\n";
|
| | | delete [] url;
|
| | | |
| | | //signal(SIGNIT,sighandler);
|
| | | env->taskScheduler().doEventLoop(&quit); // does not return
|
| | | |
| | | Medium::close(rtspServer);
|
| | | Medium::close(inputDevice);
|
| | | }
|
| | | |
| | | env->reclaim();
|
| | | delete scheduler;
|
| | | }
|
| | | } |
New file |
| | |
| | | //
|
| | | // LiveRTSPServer.h
|
| | | // FFmpegRTSPServer
|
| | | //
|
| | | // Created by Mina Saad on 9/22/15.
|
| | | // Copyright (c) 2015 Mina Saad. All rights reserved.
|
| | | //
|
| | |
|
| | | #ifndef MESAI_LIVE_RTSP_SERVER_HH
|
| | | #define MESAI_LIVE_RTSP_SERVER_HH
|
| | |
|
| | | #include <UsageEnvironment.hh>
|
| | | #include <BasicUsageEnvironment.hh>
|
| | | #include <GroupsockHelper.hh>
|
| | | #include <liveMedia.hh>
|
| | | #include "LiveServerMediaSubsession.h"
|
| | | #include "FFmpegH264Source.h"
|
| | | #include "IEncoder.h"
|
| | |
|
| | | namespace MESAI {
|
| | |
|
| | | class LiveRTSPServer
|
| | | {
|
| | | public:
|
| | |
|
| | | LiveRTSPServer(IEncoder * a_Encoder, int port, int httpPort );
|
| | | ~LiveRTSPServer();
|
| | | void run();
|
| | |
|
| | | private:
|
| | | int portNumber;
|
| | | int httpTunnelingPort;
|
| | | IEncoder * m_Encoder;
|
| | | char quit;
|
| | |
|
| | | };
|
| | | }
|
| | |
|
| | | #endif
|
New file |
| | |
| | | //
|
| | | // LiveServerMediaSubsession.cpp
|
| | | // FFmpegRTSPServer
|
| | | //
|
| | | // Created by Mina Saad on 9/22/15.
|
| | | // Copyright (c) 2015 Mina Saad. All rights reserved.
|
| | | //
|
| | |
|
| | | #include "LiveServerMediaSubsession.h"
|
| | |
|
| | | namespace MESAI
|
| | | {
|
| | | LiveServerMediaSubsession * LiveServerMediaSubsession::createNew(UsageEnvironment& env, StreamReplicator* replicator)
|
| | | { |
| | | return new LiveServerMediaSubsession(env,replicator);
|
| | | }
|
| | | |
| | | FramedSource* LiveServerMediaSubsession::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
|
| | | {
|
| | | FramedSource* source = m_replicator->createStreamReplica();
|
| | | return H264VideoStreamDiscreteFramer::createNew(envir(), source);
|
| | | }
|
| | | |
| | | RTPSink* LiveServerMediaSubsession::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource)
|
| | | {
|
| | | return H264VideoRTPSink::createNew(envir(), rtpGroupsock,rtpPayloadTypeIfDynamic);
|
| | | }
|
| | |
|
| | | }
|
New file |
| | |
| | | // |
| | | // LiveServerMediaSubsession.h |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #ifndef MESAI_Live_SERVER_MEDIA_SUBSESSION_HH |
| | | #define MESAI_Live_SERVER_MEDIA_SUBSESSION_HH |
| | | |
| | | #include <OnDemandServerMediaSubsession.hh> |
| | | #include <StreamReplicator.hh> |
| | | #include <H264VideoRTPSink.hh> |
| | | #include <H264VideoStreamFramer.hh> |
| | | #include <H264VideoStreamDiscreteFramer.hh> |
| | | #include <UsageEnvironment.hh> |
| | | #include <Groupsock.hh> |
| | | |
| | | namespace MESAI |
| | | { |
| | | |
| | | class LiveServerMediaSubsession: public OnDemandServerMediaSubsession |
| | | { |
| | | public: |
| | | static LiveServerMediaSubsession* createNew(UsageEnvironment& env, StreamReplicator* replicator); |
| | | |
| | | protected: |
| | | LiveServerMediaSubsession(UsageEnvironment& env, StreamReplicator* replicator) |
| | | : OnDemandServerMediaSubsession(env, False), m_replicator(replicator) {}; |
| | | |
| | | virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate); |
| | | virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource); |
| | | |
| | | StreamReplicator * m_replicator; |
| | | }; |
| | | |
| | | } |
| | | #endif |
New file |
| | |
| | | // |
| | | // main.cpp |
| | | // FFmpegRTSPServer |
| | | // |
| | | // Created by Mina Saad on 9/22/15. |
| | | // Copyright (c) 2015 Mina Saad. All rights reserved. |
| | | // |
| | | |
| | | #include "LiveRTSPServer.h" |
| | | #include "FFmpegH264Encoder.h" |
| | | #include "FFmpegDecoder.h" |
| | | |
| | | MESAI::FFmpegH264Encoder * encoder; |
| | | MESAI::LiveRTSPServer * server; |
| | | MESAI::FFmpegDecoder * decoder; |
| | | |
| | | int UDPPort; |
| | | int HTTPTunnelPort; |
| | | pthread_t thread1; |
| | | pthread_t thread2; |
| | | |
| | | |
| | | void * runServer(void * server) |
| | | { |
| | | ((MESAI::LiveRTSPServer * ) server)->run(); |
| | | pthread_exit(NULL); |
| | | } |
| | | |
| | | void * runEncoder(void * encoder) |
| | | { |
| | | ((MESAI::FFmpegH264Encoder * ) encoder)->run(); |
| | | pthread_exit(NULL); |
| | | } |
| | | |
| | | void onFrame(uint8_t * data) |
| | | { |
| | | encoder->SendNewFrame(data); |
| | | } |
| | | |
| | | int test_main(int argc, const char * argv[]) |
| | | { |
| | | if(argc==2) |
| | | decoder = new MESAI::FFmpegDecoder(argv[1]); |
| | | if(argc==3) |
| | | UDPPort = atoi(argv[2]); |
| | | if(argc==4) |
| | | HTTPTunnelPort = atoi(argv[3]); |
| | | decoder->intialize(); |
| | | decoder->setOnframeCallbackFunction(onFrame); |
| | | encoder = new MESAI::FFmpegH264Encoder(); |
| | | encoder->SetupVideo("dummy.avi",decoder->width,decoder->height,decoder->frameRate,decoder->GOP,decoder->bitrate); |
| | | server = new MESAI::LiveRTSPServer(encoder, UDPPort, HTTPTunnelPort); |
| | | |
| | | pthread_attr_t attr1; |
| | | pthread_attr_init(&attr1); |
| | | pthread_attr_setdetachstate(&attr1, PTHREAD_CREATE_DETACHED); |
| | | int rc1 = pthread_create(&thread1, &attr1, runServer, server); |
| | | |
| | | if (rc1){ |
| | | //exception |
| | | return -1; |
| | | } |
| | | |
| | | pthread_attr_t attr2; |
| | | pthread_attr_init(&attr2); |
| | | pthread_attr_setdetachstate(&attr2, PTHREAD_CREATE_DETACHED); |
| | | int rc2 = pthread_create(&thread2, &attr2, runEncoder, encoder); |
| | | |
| | | if (rc2){ |
| | | //exception |
| | | return -1; |
| | | } |
| | | |
| | | // Play Media Here |
| | | decoder->playMedia(); |
| | | decoder->finalize(); |
| | | |
| | | } |
| | |
| | | void rtsp_client_fmtp_callback(void* arg, const char* val);
|
| | | void rtsp_client_frame_callback(void* arg, uint8_t* buffer, size_t buffSize);
|
| | | void rtsp_client_continue_callback(void* arg);
|
| | | //struct RTSPConfig;
|
| | | #include "live555/testProgs/testRTSPClient.hpp"
|
| | |
|
| | | struct RTSPClient_Internal
|
| | | {
|
| | | PL_RTSPClient* client;
|
| | | RTSPConfig rtspConfig;
|
| | | pthread_t live_daemon_thid;
|
| | | char eventLoopWatchVariable;
|
| | |
| | | size_t lastBuffSize;
|
| | |
|
| | | RTSPClient_Internal() :
|
| | | client(nullptr), rtspConfig(), live_daemon_thid(0), |
| | | rtspConfig(), live_daemon_thid(0), |
| | | eventLoopWatchVariable(0), live_daemon_running(false),
|
| | | frame_mutex(new pthread_mutex_t), continue_mutex(new pthread_mutex_t),
|
| | | lastBuffer(nullptr), lastBuffSize(0)
|
| | |
| | |
|
| | | void reset()
|
| | | {
|
| | | client = nullptr;
|
| | | rtspConfig.progName = "";
|
| | | rtspConfig.rtspURL = "";
|
| | | RTSPConfig _rtspConfig;
|
| | | rtspConfig = _rtspConfig;
|
| | | live_daemon_thid = 0;
|
| | | eventLoopWatchVariable = 0;
|
| | | live_daemon_running = false;
|
| | |
| | | }
|
| | | };
|
| | |
|
| | | void* live_daemon_thd(void* arg)
|
| | | static void* live_daemon_thd(void* arg)
|
| | | {
|
| | | RTSPClient_Internal* in = (RTSPClient_Internal*)arg;
|
| | |
|
| | |
| | |
|
| | | usage(*env, in->rtspConfig.progName.c_str());
|
| | |
|
| | | openURL(*env, in->client, in->rtspConfig.progName.c_str(), in->rtspConfig.rtspURL.c_str());
|
| | | openURL(*env, in->rtspConfig);
|
| | |
|
| | | in->live_daemon_running = true;
|
| | | env->taskScheduler().doEventLoop(&(in->eventLoopWatchVariable));
|
| | |
| | | const RTSPConfig* config = reinterpret_cast<const RTSPConfig*>(args);
|
| | | RTSPClient_Internal* in = (RTSPClient_Internal*)internal;
|
| | | in->reset();
|
| | | in->client = this;
|
| | | in->rtspConfig = *config;
|
| | | in->rtspConfig.args = this;
|
| | |
|
| | | int ret = pthread_mutex_lock(in->frame_mutex);
|
| | | if(ret != 0)
|
| | |
| | | {
|
| | | std::string progName;
|
| | | std::string rtspURL;
|
| | | bool aux; // frame data start with 0x00000001
|
| | | int verbosityLevel;
|
| | | int tunnelOverHTTPPortNum; // portNumBits
|
| | | void* args;
|
| | |
|
| | | RTSPConfig() : progName(), rtspURL() { }
|
| | | RTSPConfig() : |
| | | progName(), rtspURL() ,aux(true), verbosityLevel(1), tunnelOverHTTPPortNum(0), args(nullptr)
|
| | | { }
|
| | | };
|
| | |
|
| | | class PL_RTSPClient : public PipeLineElem
|
| | |
| | | #include <liveMedia.hh>
|
| | | #include <BasicUsageEnvironment.hh>
|
| | |
|
| | | class MyH264FramedSource;
|
| | | #include "FFmpegRTSPServer/IEncoder.h"
|
| | | #include "FFmpegRTSPServer/LiveRTSPServer.h"
|
| | | #include "FFmpegRTSPServer/FFmpegH264Source.h"
|
| | | #include "FFmpegRTSPServer/LiveServerMediaSubsession.h"
|
| | |
|
| | | class MyEncoderStub;
|
| | |
|
| | | struct RTSPServer_Internal
|
| | | {
|
| | |
| | | pthread_mutex_t* frame_mutex;
|
| | | bool live_daemon_running;
|
| | |
|
| | | UsageEnvironment* env;
|
| | | MESAI::LiveRTSPServer * server;
|
| | | MyEncoderStub * encoderStub;
|
| | |
|
| | | // To make the second and subsequent client for each stream reuse the same
|
| | | // input stream as the first client (rather than playing the file from the
|
| | | // start for each client), change the following "False" to "True":
|
| | | Boolean reuseFirstSource;
|
| | |
|
| | | // To stream *only* MPEG-1 or 2 video "I" frames
|
| | | // (e.g., to reduce network bandwidth),
|
| | | // change the following "False" to "True":
|
| | | Boolean iFramesOnly;
|
| | | |
| | | UserAuthenticationDatabase* authDB;
|
| | |
|
| | | RTSPServer* rtspServer;//#todo delete
|
| | | |
| | | char descriptionString[1024];
|
| | | |
| | | MyH264FramedSource* pMyH264FramedSource;
|
| | | |
| | | RTSPServer_Internal() :
|
| | | buffer(nullptr), buffSize(0),
|
| | | payError(true), live_daemon_thid(0), frame_mutex(nullptr), live_daemon_running(false), |
| | | env(nullptr), reuseFirstSource(False), iFramesOnly(False), authDB(nullptr), |
| | | rtspServer(nullptr)
|
| | | payError(true), live_daemon_thid(0), frame_mutex(new pthread_mutex_t), live_daemon_running(false), |
| | | server(nullptr), encoderStub(nullptr)
|
| | | {
|
| | | pthread_mutex_init(frame_mutex, NULL);
|
| | | }
|
| | |
| | | live_daemon_thid = 0;
|
| | | live_daemon_running = false;
|
| | |
|
| | | env = nullptr;
|
| | | reuseFirstSource = False;
|
| | | iFramesOnly = False;
|
| | | authDB = nullptr; |
| | | rtspServer = nullptr;
|
| | | |
| | | strcpy(descriptionString, "Session streamed by \"testOnDemandRTSPServer\"");
|
| | | |
| | | pMyH264FramedSource = nullptr;
|
| | | server = nullptr;
|
| | | encoderStub = nullptr;
|
| | | }
|
| | | };
|
| | |
|
| | |
|
| | | class MyH264FramedSource : public FramedSource
|
| | | class MyEncoderStub : public MESAI::IEncoder
|
| | | {
|
| | | public:
|
| | | static MyH264FramedSource* createNew(UsageEnvironment& _env, RTSPServer_Internal& _in)
|
| | | MyEncoderStub(RTSPServer_Internal& _in) : in(_in)
|
| | | {
|
| | | return new MyH264FramedSource(_env, _in);
|
| | | }
|
| | |
|
| | | // deliver frame to the sink
|
| | | bool deliverFrame()
|
| | | virtual ~MyEncoderStub()
|
| | | {
|
| | | int ret = false;
|
| | | if (isCurrentlyAwaitingData()) |
| | | }
|
| | | |
| | | virtual void setCallbackFunctionFrameIsReady(std::function<void()> func)
|
| | | {
|
| | | onFrame = func;
|
| | | }
|
| | | |
| | | virtual char GetFrame(u_int8_t** FrameBuffer, unsigned int *FrameSize)
|
| | | {
|
| | | if (in.buffer != nullptr && in.buffSize > 0)
|
| | | {
|
| | | fDurationInMicroseconds = 0;
|
| | | fFrameSize = 0;
|
| | | *FrameBuffer = in.buffer;
|
| | | *FrameSize = in.buffSize;
|
| | |
|
| | | printf("send frame size=%u\n", in.buffSize);
|
| | |
|
| | | if (in.buffSize > fMaxSize) |
| | | {
|
| | | fFrameSize = fMaxSize;
|
| | | fNumTruncatedBytes = in.buffSize - fMaxSize;
|
| | | } |
| | | else |
| | | {
|
| | | fFrameSize = in.buffSize;
|
| | | }
|
| | | in.buffer = nullptr;
|
| | | in.buffSize = 0;
|
| | |
|
| | | if (fFrameSize > 0)
|
| | | {
|
| | | memcpy(fTo, in.buffer, fFrameSize);
|
| | | |
| | | int ret = pthread_mutex_unlock(in.frame_mutex);
|
| | | if(ret != 0)
|
| | | {
|
| | | printf("pthread_mutex_unlock frame_mutex: %s/n", strerror(ret));
|
| | | return false;
|
| | | }
|
| | | |
| | | ret = true;
|
| | | }
|
| | | return 1;
|
| | | }
|
| | |
|
| | | return ret;
|
| | | }
|
| | |
|
| | | protected:
|
| | | MyH264FramedSource(UsageEnvironment& _env, RTSPServer_Internal& _in) : |
| | | FramedSource(_env), env(_env), in(_in)
|
| | | {
|
| | | else
|
| | | {
|
| | | ReleaseFrame();
|
| | | return 0;
|
| | | }
|
| | | }
|
| | |
|
| | | virtual ~MyH264FramedSource()
|
| | | virtual char ReleaseFrame()
|
| | | {
|
| | | int ret = pthread_mutex_unlock(in.frame_mutex);
|
| | | if(ret != 0)
|
| | | {
|
| | | printf("pthread_mutex_unlock frame_mutex: %s/n", strerror(ret));
|
| | | return 0;
|
| | | }
|
| | | |
| | | return 1;
|
| | | }
|
| | |
|
| | | // overide FramedSource
|
| | | virtual void doGetNextFrame()
|
| | | void deliverFrame()
|
| | | {
|
| | | printf("MyH264FramedSource::doGetNextFrame\n");
|
| | | |
| | | // write frame buffer of RTSPServer_Internal::buffer
|
| | | onFrame();
|
| | |
|
| | | int ret = pthread_mutex_lock(in.frame_mutex);
|
| | | if(ret != 0)
|
| | | {
|
| | | printf("pthread_mutex_lock frame_mutex: %s/n", strerror(ret));
|
| | | return;
|
| | | }
|
| | | }
|
| | |
|
| | | // deliverFrame
|
| | | //if (fFrameSize > 0)
|
| | | //{
|
| | | // send Frame to the consumer
|
| | | FramedSource::afterGetting(this); |
| | | //}
|
| | | }
|
| | | |
| | | virtual void doStopGettingFrames()
|
| | | {
|
| | | FramedSource::doStopGettingFrames();
|
| | | }
|
| | | |
| | | private:
|
| | | UsageEnvironment& env;
|
| | | RTSPServer_Internal& in;
|
| | | std::function<void()> onFrame;
|
| | | };
|
| | |
|
| | | PipeLineElem* create_PL_RTSPServer()
|
| | |
| | | PL_RTSPServer::~PL_RTSPServer()
|
| | | {
|
| | | delete (RTSPServer_Internal*)internal;
|
| | | internal= nullptr;
|
| | | internal = nullptr;
|
| | | }
|
| | |
|
| | | void* live_daemon_thd(void* arg)
|
| | | static void* live_daemon_thd(void* arg)
|
| | | {
|
| | | RTSPServer_Internal* in = (RTSPServer_Internal*)arg;
|
| | |
|
| | | MyEncoderStub encoder(*in);
|
| | | in->encoderStub = &encoder;
|
| | | in->server = new MESAI::LiveRTSPServer(&encoder, 8554, 8080);
|
| | |
|
| | | // Begin by setting up our usage environment:
|
| | | TaskScheduler* scheduler = BasicTaskScheduler::createNew();
|
| | | in->env = BasicUsageEnvironment::createNew(*scheduler);
|
| | |
|
| | | #ifdef ACCESS_CONTROL
|
| | | // To implement client access control to the RTSP server, do the following:
|
| | | in->authDB = new UserAuthenticationDatabase;
|
| | | in->authDB->addUserRecord("username1", "password1"); // replace these with real strings
|
| | | // Repeat the above with each <username>, <password> that you wish to allow
|
| | | // access to the server.
|
| | | #endif
|
| | |
|
| | | // Create the RTSP server:
|
| | | in->rtspServer = RTSPServer::createNew(*(in->env), 8554, in->authDB);
|
| | | if (in->rtspServer == NULL)
|
| | | {
|
| | | *(in->env) << "Failed to create RTSP server: " << in->env->getResultMsg() << "\n";
|
| | | return nullptr;
|
| | | }
|
| | |
|
| | | // Set up each of the possible streams that can be served by the
|
| | | // RTSP server. Each such stream is implemented using a
|
| | | // "ServerMediaSession" object, plus one or more
|
| | | // "ServerMediaSubsession" objects for each audio/video substream.
|
| | | |
| | | char const* streamName = "plH264Encoder";
|
| | | ServerMediaSession* sms = ServerMediaSession::createNew(*(in->env), streamName, streamName, in->descriptionString);
|
| | | in->pMyH264FramedSource = MyH264FramedSource::createNew(*(in->env), *in);
|
| | | sms->addSubsession(in->pMyH264FramedSource);
|
| | | in->rtspServer->addServerMediaSession(sms);
|
| | | |
| | | // announceStream
|
| | | char* url = in->rtspServer->rtspURL(sms);
|
| | | *(in->env) << "Play this stream using the URL " << url << "\n";
|
| | | delete[] url;
|
| | | |
| | | // Also, attempt to create a HTTP server for RTSP-over-HTTP tunneling.
|
| | | // Try first with the default HTTP port (80), and then with the alternative HTTP
|
| | | // port numbers (8000 and 8080).
|
| | |
|
| | | if (in->rtspServer->setUpTunnelingOverHTTP(80))
|
| | | *(in->env) << "\n(We use port " << in->rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling.)\n";
|
| | | else
|
| | | *(in->env) << "\n(RTSP-over-HTTP tunneling is not available.)\n";
|
| | |
|
| | | in->live_daemon_running = true;
|
| | | in->env->taskScheduler().doEventLoop(); // does not return
|
| | | in->server->run(); // does not return
|
| | | in->encoderStub = nullptr;
|
| | | in->live_daemon_running = false;
|
| | | }
|
| | |
|
| | |
| | | {
|
| | | RTSPServer_Internal* in = (RTSPServer_Internal*)internal;
|
| | | in->reset();
|
| | | |
| | | int ret = pthread_mutex_lock(in->frame_mutex);
|
| | | if(ret != 0)
|
| | | {
|
| | | printf("pthread_mutex_lock frame_mutex: %s/n", strerror(ret));
|
| | | return false;
|
| | | }
|
| | | |
| | | ret = pthread_create(&(in->live_daemon_thid), NULL, live_daemon_thd, in);
|
| | |
|
| | | int ret = pthread_create(&(in->live_daemon_thid), NULL, live_daemon_thd, in);
|
| | | if(ret != 0)
|
| | | {
|
| | | printf("pthread_create: %s/n", strerror(ret));
|
| | |
| | | {
|
| | | RTSPServer_Internal* in = (RTSPServer_Internal*)internal;
|
| | |
|
| | | if (pm.buffer == nullptr || pm.buffSize <= 0)
|
| | | return false;
|
| | | |
| | | in->buffer = pm.buffer;
|
| | | in->buffSize = pm.buffSize;
|
| | |
|
| | | return in->pMyH264FramedSource->deliverFrame();
|
| | | if (in->encoderStub == nullptr)
|
| | | return false;
|
| | | |
| | | in->encoderStub->deliverFrame();
|
| | | return true;
|
| | | }
|
| | |
|
| | | bool PL_RTSPServer::gain(PipeMaterial& pm)
|
| | |
| | | |
| | | #include <iostream> |
| | | |
| | | #define RTSP_CLIENT_VERBOSITY_LEVEL 1 // by default, print verbose output from each "RTSPClient" |
| | | |
| | | // By default, we request that the server stream its data using RTP/UDP. |
| | | // If, instead, you want to request that the server stream via RTP-over-TCP, change the following to True: |
| | | #define REQUEST_STREAMING_OVER_TCP False |
| | | |
| | | // Even though we're not going to be doing anything with the incoming data, we still need to receive it. |
| | | // Define the size of the buffer that we'll use: |
| | | #define DUMMY_SINK_RECEIVE_BUFFER_SIZE 1920*1080*3 |
| | | |
| | | // If you don't want to see debugging output for each received frame, then comment out the following line: |
| | | #define DEBUG_PRINT_EACH_RECEIVED_FRAME 1 |
| | | |
| | | // Forward function definitions: |
| | | |
| | | // RTSP 'response handlers': |
| | |
| | | // called at the end of a stream's expected duration (if the stream has not already signaled its end using a RTCP "BYE") |
| | | |
| | | // The main streaming routine (for each "rtsp://" URL): |
| | | void openURL(UsageEnvironment& env, void* args, char const* progName, char const* rtspURL); |
| | | void openURL(UsageEnvironment& env, const RTSPConfig& _rtspConfig); |
| | | |
| | | // Used to iterate through each stream's 'subsessions', setting up each one: |
| | | void setupNextSubsession(RTSPClient* rtspClient); |
| | |
| | | return 1; |
| | | } |
| | | |
| | | RTSPConfig rtspConfig; |
| | | rtspConfig.progName = argv[0]; |
| | | rtspConfig.rtspURL = ""; |
| | | rtspConfig.aux = false; |
| | | rtspConfig.verbosityLevel = RTSP_CLIENT_VERBOSITY_LEVEL; |
| | | rtspConfig.tunnelOverHTTPPortNum = 0; |
| | | rtspConfig.args = nullptr; |
| | | |
| | | // There are argc-1 URLs: argv[1] through argv[argc-1]. Open and start streaming each one: |
| | | for (int i = 1; i <= argc-1; ++i) { |
| | | openURL(*env, NULL, argv[0], argv[i]); |
| | | rtspConfig.rtspURL = argv[i]; |
| | | openURL(*env, rtspConfig); |
| | | } |
| | | |
| | | // All subsequent activity takes place within the event loop: |
| | |
| | | |
| | | class ourRTSPClient: public RTSPClient { |
| | | public: |
| | | static ourRTSPClient* createNew(UsageEnvironment& env, char const* rtspURL, |
| | | int verbosityLevel = 0, |
| | | char const* applicationName = NULL, |
| | | portNumBits tunnelOverHTTPPortNum = 0); |
| | | static ourRTSPClient* createNew(UsageEnvironment& env, const RTSPConfig& _rtspConfig); |
| | | |
| | | protected: |
| | | ourRTSPClient(UsageEnvironment& env, char const* rtspURL, |
| | | int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum); |
| | | ourRTSPClient(UsageEnvironment& env, const RTSPConfig& _rtspConfig); |
| | | // called only by createNew(); |
| | | virtual ~ourRTSPClient(); |
| | | |
| | | public: |
| | | StreamClientState scs; |
| | | void* args; |
| | | const RTSPConfig& rtspConfig; |
| | | }; |
| | | |
| | | // Define a data sink (a subclass of "MediaSink") to receive the data for each subsession (i.e., each audio or video 'substream'). |
| | |
| | | { |
| | | public: |
| | | static DummySink* createNew(UsageEnvironment& env, |
| | | void* _args, |
| | | const RTSPConfig& _rtspConfig, |
| | | MediaSubsession& subsession, // identifies the kind of data that's being received |
| | | char const* streamId = NULL); // identifies the stream itself (optional) |
| | | |
| | | private: |
| | | DummySink(UsageEnvironment& env, void* _args, MediaSubsession& subsession, char const* streamId); |
| | | DummySink(UsageEnvironment& env, const RTSPConfig& _rtspConfig, MediaSubsession& subsession, char const* streamId); |
| | | // called only by "createNew()" |
| | | virtual ~DummySink(); |
| | | |
| | |
| | | struct timeval presentationTime, unsigned durationInMicroseconds); |
| | | |
| | | public: |
| | | void* args; |
| | | const RTSPConfig& rtspConfig; |
| | | |
| | | private: |
| | | // redefined virtual functions: |
| | |
| | | char* fStreamId; |
| | | }; |
| | | |
| | | #define RTSP_CLIENT_VERBOSITY_LEVEL 1 // by default, print verbose output from each "RTSPClient" |
| | | |
| | | static unsigned rtspClientCount = 0; // Counts how many streams (i.e., "RTSPClient"s) are currently in use. |
| | | |
| | | void openURL(UsageEnvironment& env, void* args, char const* progName, char const* rtspURL) { |
| | | // Begin by creating a "RTSPClient" object. Note that there is a separate "RTSPClient" object for each stream that we wish |
| | | // to receive (even if more than stream uses the same "rtsp://" URL). |
| | | RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName); |
| | | if (rtspClient == NULL) { |
| | | env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n"; |
| | | return; |
| | | } |
| | | |
| | | ((ourRTSPClient*)rtspClient)->args = args; |
| | | void openURL(UsageEnvironment& env, const RTSPConfig& _rtspConfig) |
| | | { |
| | | // Begin by creating a "RTSPClient" object. Note that there is a separate "RTSPClient" object for each stream that we wish |
| | | // to receive (even if more than stream uses the same "rtsp://" URL). |
| | | RTSPClient* rtspClient = ourRTSPClient::createNew(env, _rtspConfig); |
| | | if (rtspClient == NULL) |
| | | { |
| | | env << "Failed to create a RTSP client for URL \"" << _rtspConfig.rtspURL.c_str() << "\": " << env.getResultMsg() << "\n"; |
| | | return; |
| | | } |
| | | |
| | | ++rtspClientCount; |
| | | ++rtspClientCount; |
| | | |
| | | // Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream. |
| | | // Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response. |
| | | // Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop: |
| | | rtspClient->sendDescribeCommand(continueAfterDESCRIBE); |
| | | // Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream. |
| | | // Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response. |
| | | // Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop: |
| | | rtspClient->sendDescribeCommand(continueAfterDESCRIBE); |
| | | } |
| | | |
| | | |
| | | // Implementation of the RTSP 'response handlers': |
| | | |
| | | void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) { |
| | | do { |
| | | UsageEnvironment& env = rtspClient->envir(); // alias |
| | | StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias |
| | | void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) |
| | | { |
| | | do |
| | | { |
| | | UsageEnvironment& env = rtspClient->envir(); // alias |
| | | StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias |
| | | |
| | | if (resultCode != 0) { |
| | | env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n"; |
| | | delete[] resultString; |
| | | break; |
| | | } |
| | | if (resultCode != 0) |
| | | { |
| | | env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n"; |
| | | delete[] resultString; |
| | | break; |
| | | } |
| | | |
| | | char* const sdpDescription = resultString; |
| | | env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n"; |
| | | char* const sdpDescription = resultString; |
| | | env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n"; |
| | | |
| | | // Create a media session object from this SDP description: |
| | | scs.session = MediaSession::createNew(env, sdpDescription); |
| | | delete[] sdpDescription; // because we don't need it anymore |
| | | if (scs.session == NULL) { |
| | | env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n"; |
| | | break; |
| | | } else if (!scs.session->hasSubsessions()) { |
| | | env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n"; |
| | | break; |
| | | } |
| | | // Create a media session object from this SDP description: |
| | | scs.session = MediaSession::createNew(env, sdpDescription); |
| | | delete[] sdpDescription; // because we don't need it anymore |
| | | if (scs.session == NULL) |
| | | { |
| | | env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n"; |
| | | break; |
| | | } |
| | | else if (!scs.session->hasSubsessions()) |
| | | { |
| | | env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n"; |
| | | break; |
| | | } |
| | | |
| | | // Then, create and set up our data source objects for the session. We do this by iterating over the session's 'subsessions', |
| | | // calling "MediaSubsession::initiate()", and then sending a RTSP "SETUP" command, on each one. |
| | | // (Each 'subsession' will have its own data source.) |
| | | scs.iter = new MediaSubsessionIterator(*scs.session); |
| | | setupNextSubsession(rtspClient); |
| | | return; |
| | | } while (0); |
| | | // Then, create and set up our data source objects for the session. We do this by iterating over the session's 'subsessions', |
| | | // calling "MediaSubsession::initiate()", and then sending a RTSP "SETUP" command, on each one. |
| | | // (Each 'subsession' will have its own data source.) |
| | | scs.iter = new MediaSubsessionIterator(*scs.session); |
| | | setupNextSubsession(rtspClient); |
| | | return; |
| | | } while (0); |
| | | |
| | | // An unrecoverable error occurred with this stream. |
| | | shutdownStream(rtspClient); |
| | | // An unrecoverable error occurred with this stream. |
| | | shutdownStream(rtspClient); |
| | | } |
| | | |
| | | // By default, we request that the server stream its data using RTP/UDP. |
| | | // If, instead, you want to request that the server stream via RTP-over-TCP, change the following to True: |
| | | #define REQUEST_STREAMING_OVER_TCP False |
| | | |
| | | void setupNextSubsession(RTSPClient* rtspClient) { |
| | | void setupNextSubsession(RTSPClient* rtspClient) |
| | | { |
| | | UsageEnvironment& env = rtspClient->envir(); // alias |
| | | StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias |
| | | |
| | |
| | | // (This will prepare the data sink to receive data; the actual flow of data from the client won't start happening until later, |
| | | // after we've sent a RTSP "PLAY" command.) |
| | | |
| | | DummySink* mySink; |
| | | scs.subsession->sink = mySink = DummySink::createNew(env, ((ourRTSPClient*)rtspClient)->args, |
| | | *scs.subsession, rtspClient->url()); |
| | | scs.subsession->sink = DummySink::createNew(env, ((ourRTSPClient*)rtspClient)->rtspConfig, |
| | | *scs.subsession, rtspClient->url()); |
| | | // perhaps use your own custom "MediaSink" subclass instead |
| | | if (scs.subsession->sink == NULL) { |
| | | env << *rtspClient << "Failed to create a data sink for the \"" << *scs.subsession |
| | |
| | | |
| | | // Implementation of "ourRTSPClient": |
| | | |
| | | ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, char const* rtspURL, |
| | | int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum) { |
| | | return new ourRTSPClient(env, rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum); |
| | | ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, const RTSPConfig& _rtspConfig) |
| | | { |
| | | return new ourRTSPClient(env, _rtspConfig); |
| | | } |
| | | |
| | | ourRTSPClient::ourRTSPClient(UsageEnvironment& env, char const* rtspURL, |
| | | int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum) |
| | | : RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum, -1), |
| | | args(nullptr) |
| | | ourRTSPClient::ourRTSPClient(UsageEnvironment& env, const RTSPConfig& _rtspConfig) |
| | | : RTSPClient(env, _rtspConfig.rtspURL.c_str(), _rtspConfig.verbosityLevel, _rtspConfig.progName.c_str(), |
| | | _rtspConfig.tunnelOverHTTPPortNum, -1), rtspConfig(_rtspConfig) |
| | | { |
| | | } |
| | | |
| | |
| | | |
| | | // Implementation of "DummySink": |
| | | |
| | | // Even though we're not going to be doing anything with the incoming data, we still need to receive it. |
| | | // Define the size of the buffer that we'll use: |
| | | #define DUMMY_SINK_RECEIVE_BUFFER_SIZE 1920*1080*3 |
| | | |
| | | DummySink* DummySink::createNew(UsageEnvironment& env, void* _args, MediaSubsession& subsession, char const* streamId) |
| | | DummySink* DummySink::createNew(UsageEnvironment& env, const RTSPConfig& _rtspConfig, MediaSubsession& subsession, char const* streamId) |
| | | { |
| | | return new DummySink(env, _args, subsession, streamId); |
| | | return new DummySink(env, _rtspConfig, subsession, streamId); |
| | | } |
| | | |
| | | DummySink::DummySink(UsageEnvironment& env, void* _args, MediaSubsession& subsession, char const* streamId) |
| | | : MediaSink(env), args(_args), fSubsession(subsession) |
| | | DummySink::DummySink(UsageEnvironment& env, const RTSPConfig& _rtspConfig, MediaSubsession& subsession, char const* streamId) |
| | | : MediaSink(env), rtspConfig(_rtspConfig), fSubsession(subsession) |
| | | { |
| | | fStreamId = strDup(streamId); |
| | | fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE]; |
| | | |
| | | // ffmpeg need AUX header |
| | | fReceiveBuffer[0]=0x00; fReceiveBuffer[1]=0x00; fReceiveBuffer[2]=0x00; fReceiveBuffer[3]=0x01; |
| | | if (rtspConfig.aux) |
| | | { |
| | | fReceiveBuffer[0]=0x00; fReceiveBuffer[1]=0x00; fReceiveBuffer[2]=0x00; fReceiveBuffer[3]=0x01; |
| | | } |
| | | |
| | | //parse sdp |
| | | const char* strSDP = fSubsession.savedSDPLines(); |
| | | rtsp_client_sdp_callback(args, strSDP); |
| | | rtsp_client_sdp_callback(rtspConfig.args, strSDP); |
| | | |
| | | const char* strFmtp = fSubsession.fmtp_spropparametersets(); |
| | | rtsp_client_fmtp_callback(args, strFmtp); |
| | | rtsp_client_fmtp_callback(rtspConfig.args, strFmtp); |
| | | //std::cout << strFmtp << std::endl; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes, |
| | | struct timeval presentationTime, unsigned durationInMicroseconds) { |
| | | DummySink* sink = (DummySink*)clientData; |
| | | struct timeval presentationTime, unsigned durationInMicroseconds) |
| | | { |
| | | DummySink* sink = (DummySink*)clientData; |
| | | |
| | | if (frameSize > 0) |
| | | rtsp_client_frame_callback(sink->args, sink->fReceiveBuffer, frameSize + 4); |
| | | if (frameSize > 0) |
| | | { |
| | | unsigned s = frameSize; |
| | | if (sink->rtspConfig.aux) |
| | | s += 4; |
| | | rtsp_client_frame_callback(sink->rtspConfig.args, sink->fReceiveBuffer, s); |
| | | } |
| | | |
| | | sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds); |
| | | sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds); |
| | | } |
| | | |
| | | // If you don't want to see debugging output for each received frame, then comment out the following line: |
| | | #define DEBUG_PRINT_EACH_RECEIVED_FRAME 1 |
| | | |
| | | void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, |
| | | struct timeval presentationTime, unsigned /*durationInMicroseconds*/) { |
| | |
| | | continuePlaying(); |
| | | } |
| | | |
| | | Boolean DummySink::continuePlaying() { |
| | | if (fSource == NULL) return False; // sanity check (should not happen) |
| | | Boolean DummySink::continuePlaying() |
| | | { |
| | | if (fSource == NULL) return False; // sanity check (should not happen) |
| | | |
| | | rtsp_client_continue_callback(args); |
| | | |
| | | // Request the next frame of data from our input source. "afterGettingFrame()" will get called later, when it arrives: |
| | | fSource->getNextFrame(fReceiveBuffer + 4, DUMMY_SINK_RECEIVE_BUFFER_SIZE, |
| | | afterGettingFrame, this, |
| | | onSourceClosure, this); |
| | | return True; |
| | | rtsp_client_continue_callback(rtspConfig.args); |
| | | |
| | | u_int8_t* b = fReceiveBuffer; |
| | | if (rtspConfig.aux) |
| | | b += 4; |
| | | |
| | | // Request the next frame of data from our input source. "afterGettingFrame()" will get called later, when it arrives: |
| | | fSource->getNextFrame(b, DUMMY_SINK_RECEIVE_BUFFER_SIZE, |
| | | afterGettingFrame, this, |
| | | onSourceClosure, this); |
| | | |
| | | return True; |
| | | } |
| | |
| | | PipeLine pipeLine;
|
| | |
|
| | | pipeLine.register_elem_creator("PL_RTSPClient", create_PL_RTSPClient);
|
| | | pipeLine.register_elem_creator("PL_RTSPServer", create_PL_RTSPServer);
|
| | | pipeLine.register_elem_creator("PL_H264Decoder", create_PL_H264Decoder);
|
| | | pipeLine.register_elem_creator("PL_AVFrameYUV420", create_PL_AVFrameYUV420);
|
| | | pipeLine.register_elem_creator("PL_H264Encoder", create_PL_H264Encoder);
|
| | |
| | | RTSPConfig rtspConfig;
|
| | | rtspConfig.progName = argv[0];
|
| | | rtspConfig.rtspURL = argv[1];
|
| | | rtspConfig.aux = false; // ffmpeg need aux
|
| | | rtspConfig.verbosityLevel = 1;
|
| | | rtspConfig.tunnelOverHTTPPortNum = 0;
|
| | | rtspConfig.args = nullptr;
|
| | | bool ret = rtspClient->init(&rtspConfig);
|
| | | if (!ret)
|
| | | {
|
| | |
| | | exit(EXIT_FAILURE);
|
| | | }
|
| | |
|
| | | PL_H264Decoder* h264Decoder = (PL_H264Decoder*)pipeLine.push_elem("PL_H264Decoder");
|
| | | h264Decoder->init(nullptr);
|
| | | //PL_H264Decoder* h264Decoder = (PL_H264Decoder*)pipeLine.push_elem("PL_H264Decoder");
|
| | | //h264Decoder->init(nullptr);
|
| | |
|
| | | //PL_AVFrameYUV420* avFrameYUV420 = (PL_AVFrameYUV420*)pipeLine.push_elem("PL_AVFrameYUV420");
|
| | | //avFrameYUV420->init(nullptr);
|
| | |
| | | LIBYUV_INC="-I$LIBYUV_BASE/include" |
| | | LIBYUV_LIB="-L$LIBYUV_BASE -lyuv" |
| | | |
| | | FFMPEGRTSPSERVER_BASE=./FFmpegRTSPServer |
| | | FFMPEGRTSPSERVER_OBJ="FFmpegH264Source.o LiveRTSPServer.o LiveServerMediaSubsession.o" |
| | | |
| | | CPPFLAGS+="-pthread $LIVEMEDIA_INC $FFMPEG_INC $LIBBASE64_INC $LIBYUV_INC" |
| | | LDFLAGS+="-pthread $LIVEMEDIA_LIB $FFMPEG_LIB $LIBBASE64_LIB $LIBYUV_LIB $LIBX264_LIB" |
| | | |
| | |
| | | g++ -g -c -std=c++11 PL_AVFrameYUV420.cpp $CFLAGS $CPPFLAGS |
| | | g++ -g -c -std=c++11 PL_AVFrameBGRA.cpp $CFLAGS $CPPFLAGS |
| | | g++ -g -c -std=c++11 PipeLine.cpp $CFLAGS $CPPFLAGS |
| | | |
| | | g++ -g -c -std=c++11 $FFMPEGRTSPSERVER_BASE/LiveRTSPServer.cpp $CFLAGS $CPPFLAGS |
| | | g++ -g -c -std=c++11 $FFMPEGRTSPSERVER_BASE/FFmpegH264Source.cpp $CFLAGS $CPPFLAGS |
| | | g++ -g -c -std=c++11 $FFMPEGRTSPSERVER_BASE/LiveServerMediaSubsession.cpp $CFLAGS $CPPFLAGS |
| | | |
| | | g++ -g -std=c++11 \ |
| | | main.o PL_RTSPClient.o PL_RTSPServer.o PL_H264Decoder.o PL_H264Encoder.o PL_AVFrameYUV420.o PL_AVFrameBGRA.o PipeLine.o \ |
| | | main.o PipeLine.o \ |
| | | PL_RTSPClient.o PL_H264Decoder.o PL_H264Encoder.o PL_AVFrameYUV420.o PL_AVFrameBGRA.o \ |
| | | $FFMPEGRTSPSERVER_OBJ PL_RTSPServer.o \ |
| | | $LDFLAGS -o rtsp_face |
| | | |
| | | #export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBX264_BASE/lib:$FFMPEG_BASE/lib |
| | | #./rtsp_face rtsp://admin:admin12345@192.168.1.63:554/h264/ch1/main/av_stream |
| | | #./rtsp_face rtsp://admin:admin12345@192.168.1.64:554/h264/ch1/main/av_stream |