From 03a7bb21201086ff15339393fabbce73b0c67ebb Mon Sep 17 00:00:00 2001 From: "DESKTOP-4RNDQIC\\29019" <290198252@qq.com> Date: Fri, 29 May 2020 01:16:10 +0800 Subject: [PATCH] =?UTF-8?q?PC=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0rtmp=20pusher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + client/qt_gl_render/yuvgl/mainwindow.cpp | 8 +- .../yuvgl/media/CameraCapture.cpp | 1 - .../qt_gl_render/yuvgl/media/RtmpPuller.cpp | 132 ++ .../qt_gl_render/yuvgl/media/RtmpPuller2.cpp | 251 ++++ .../qt_gl_render/yuvgl/media/RtmpPusher.cpp | 570 ++++++++ client/qt_gl_render/yuvgl/media/RtmpPusher.h | 1 + client/qt_gl_render/yuvgl/yuvgl.pro | 3 + client/qt_gl_render/yuvgl/yuvgl.pro.user | 1291 +++++++++++++++++ .../hisi_rtsp_demo-master/rtsp_lib/rtsp.log | 97 ++ device/mpp/sample/venc/a.exe | Bin 0 -> 61568 bytes 11 files changed, 2350 insertions(+), 5 deletions(-) create mode 100644 client/qt_gl_render/yuvgl/media/RtmpPuller.cpp create mode 100644 client/qt_gl_render/yuvgl/media/RtmpPuller2.cpp create mode 100644 client/qt_gl_render/yuvgl/media/RtmpPusher.cpp create mode 100644 client/qt_gl_render/yuvgl/yuvgl.pro.user create mode 100644 device/mpp/sample/hisi_rtsp_demo-master/rtsp_lib/rtsp.log create mode 100644 device/mpp/sample/venc/a.exe diff --git a/.gitignore b/.gitignore index 0336669..78a39a1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_32_bit-Release/ client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MSVC2017_64bit-Debug/ client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_64_bit-Debug/ client/qt_gl_render/yuvgl/third/libs/ +client/qt_gl_render/yuvgl/third/ diff --git a/client/qt_gl_render/yuvgl/mainwindow.cpp b/client/qt_gl_render/yuvgl/mainwindow.cpp index ab5c092..c9be8f6 100644 --- a/client/qt_gl_render/yuvgl/mainwindow.cpp +++ b/client/qt_gl_render/yuvgl/mainwindow.cpp @@ -24,8 +24,8 @@ void MainWindow::on_pushButton_clicked(){ mPlayerWidget = new CPlayWidget(nullptr); } if(!m_bCameraOpen){ - ui->widget->SetDataType(CPlayWidget::IMG_TYPE::TYPE_RGB32); - ui->widget->SetImgSize(640,480); + mPlayerWidget->SetDataType(CPlayWidget::IMG_TYPE::TYPE_RGB32); + mPlayerWidget->SetImgSize(640,480); qDebug()<comboBox->currentText().size()<comboBox->currentText(); wchar_t *opencamera = new wchar_t[ui->comboBox->currentText().size()]; @@ -34,11 +34,11 @@ void MainWindow::on_pushButton_clicked(){ if(nullptr == mCamera){ this->mCamera = new Camera(ss); } - this->mCamera->SetObserver(ui->widget); + this->mCamera->SetObserver(mPlayerWidget); qDebug()<comboBox->currentText(); ui->pushButton->setText("关闭摄像头"); m_bCameraOpen = true; - ui->widget->show(); + mPlayerWidget->show(); }else{ m_bCameraOpen = false; ui->pushButton->setText("打开摄像头"); diff --git a/client/qt_gl_render/yuvgl/media/CameraCapture.cpp b/client/qt_gl_render/yuvgl/media/CameraCapture.cpp index c18f2fd..1272247 100644 --- a/client/qt_gl_render/yuvgl/media/CameraCapture.cpp +++ b/client/qt_gl_render/yuvgl/media/CameraCapture.cpp @@ -403,7 +403,6 @@ HRESULT STDMETHODCALLTYPE Camera::SampleGrabberCallback::SampleCB(double Time, I HRESULT STDMETHODCALLTYPE Camera::SampleGrabberCallback::BufferCB(double Time, BYTE * pBuffer, long BufferLen) { -#define DEBUG_CAMERA #ifdef DEBUG_CAMERA static FILE *p = fopen("camera_test.yuv","wb+"); fwrite(pBuffer,BufferLen,1,p); diff --git a/client/qt_gl_render/yuvgl/media/RtmpPuller.cpp b/client/qt_gl_render/yuvgl/media/RtmpPuller.cpp new file mode 100644 index 0000000..356712a --- /dev/null +++ b/client/qt_gl_render/yuvgl/media/RtmpPuller.cpp @@ -0,0 +1,132 @@ +#include "RtmpPuller.h" + + +RtmpPuller::RtmpPuller() + :mFrameIndex(0), mAudioIndex(-1),mVideoIndex(-1) + , mAudioStream(nullptr),mVideoStream(nullptr) +{ + av_register_all(); + //Network + avformat_network_init(); + //Input +} + +int RtmpPuller::ConnectServer(const char *p) +{ + int ret = 0; + if ((ret = avformat_open_input(&mIfmtCtx, p, 0, 0)) < 0) { + printf("Could not open input file."); + return -1; + } + if ((ret = avformat_find_stream_info(mIfmtCtx, 0)) < 0) { + printf("Failed to retrieve input stream information"); + return -1; + } + for (int i = 0; i < mIfmtCtx->nb_streams; i++) { + if (mIfmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + mVideoIndex = i; + } + if (mIfmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + mAudioIndex = i; + } + } + if(mAudioIndex > -1) + this->mAudioStream = mIfmtCtx->streams[mAudioIndex]; + if(mVideoIndex > -1) + this->mVideoStream = mIfmtCtx->streams[mVideoIndex]; + av_dump_format(mIfmtCtx, 0, p, 0); + mH264bsfc = av_bitstream_filter_init("h264_mp4toannexb"); + mStatus = RUNNING; + if((mAudioIndex == -1 ) &&(mVideoIndex == -1)) + mStatus = NOSOURCE; + return 0; +} + +int ThreadPull(RtmpPuller*p) { + while (p->Status() == RtmpPuller::CAP_STATUS::RUNNING) { + p->PullData(); + } + return 0; +} + +int RtmpPuller::StartPull() +{ + this->mThread = new std::thread(ThreadPull, this); + this->mThread->get_id(); + mStatus = RUNNING; + return 0; +} + +int RtmpPuller::PullData() +{ + static int drop = 0; + AVStream *in_stream; + //Get an AVPacket + int ret = av_read_frame(mIfmtCtx, &pkt); + if (ret < 0) + return -1; + in_stream = mIfmtCtx->streams[pkt.stream_index]; + /* copy packet */ + //Convert PTS/DTS + pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, in_stream->time_base, + (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); + pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, in_stream->time_base, + (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); + pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, in_stream->time_base); + pkt.pos = -1; + //Print to Screen + if (drop < 100) { + drop++; + goto end; + } + if (pkt.stream_index == mVideoIndex) { + printf("Receive %8d video frames from input URL\n", mFrameIndex); + mFrameIndex++; + av_bitstream_filter_filter(mH264bsfc, in_stream->codec, NULL, + &pkt.data, &pkt.size, pkt.data, pkt.size, 0); + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Video) { + p->OnRtmpFrame(pkt.data, pkt.size); + } + } + } + } + if (pkt.stream_index == mAudioIndex) { + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Audio) { + p->OnRtmpFrame(pkt.data, pkt.size); + } + } + } + } +end: + //printf("%02x %02x %02x %02x %02x\r\n", pkt.data[0], pkt.data[1], pkt.data[2], pkt.data[3], pkt.data[4]); + av_free_packet(&pkt); +} + +int RtmpPuller::SetObserver(RtmpPullObserver *p) +{ + if (nullptr == p) + return -1; + mMux.lock(); + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + if (p == *itr) return 0; + } + this->mObserver.push_back(p); + mMux.unlock(); + return 0; +} + +RtmpPuller::CAP_STATUS RtmpPuller::Status() +{ + return this->mStatus; +} + +AVStream * RtmpPuller::AudioStream() +{ + return this->mAudioStream; +} diff --git a/client/qt_gl_render/yuvgl/media/RtmpPuller2.cpp b/client/qt_gl_render/yuvgl/media/RtmpPuller2.cpp new file mode 100644 index 0000000..b97b573 --- /dev/null +++ b/client/qt_gl_render/yuvgl/media/RtmpPuller2.cpp @@ -0,0 +1,251 @@ +#include "RtmpPuller2.h" +#include "Debuger.h" +RtmpPuller2::RtmpPuller2() +{ + + mAccBuffer = new uint8_t[3000]; +} +RtmpPuller2::~RtmpPuller2() +{ +} + +int ThreadPull(RtmpPuller2*p) { + while (p->Status() == RtmpPuller2::CAP_STATUS::RUNNING) { + p->PullData(); + Sleep(10); + } + return 0; +} +// ر +int RtmpPuller2::StopPull() +{ + mStatus = STOP; + this->mThread->join(); + RTMP_Close(mRtmp); + RTMP_Free(mRtmp); + return 0; +} + +int RtmpPuller2::StartPull() +{ + if(this->mStatus == CONNECTED) { + mStatus = RUNNING; + this->mThread = new std::thread(ThreadPull, this); + this->mThread->get_id(); + } + else { + } + return 0; +} + +FILE *fp = nullptr; +int RtmpPuller2::PullData() +{ + RTMPPacket packet = { 0 }; + // Parse rtmp stream to h264 and aac + uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 }; + int ret = RTMP_ReadPacket(mRtmp, &packet); + if (ret < 0) + return ret; + + if (nullptr == fp) { + fp = fopen("src.aac", "wb"); + } + if (RTMPPacket_IsReady(&packet)) { + // Process packet, eg: set chunk size, set bw, ... + RTMP_ClientPacket(mRtmp, &packet); + if (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) { + bool keyframe = 0x17 == packet.m_body[0] ? true : false; + bool sequence = 0x00 == packet.m_body[1]; + printf("keyframe=%s, sequence=%s\n", keyframe ? "true" : "false", sequence ? "true" : "false"); + // SPS/PPS sequence + if (sequence) { + uint32_t offset = 10; + uint32_t sps_num = packet.m_body[offset++] & 0x1f; + for (int i = 0; i < sps_num; i++) { + uint8_t ch0 = packet.m_body[offset]; + uint8_t ch1 = packet.m_body[offset + 1]; + uint32_t sps_len = ((ch0 << 8) | ch1); + offset += 2; + packet.m_body[offset - 1] = 0x01; + packet.m_body[offset - 2] = 0x00; + packet.m_body[offset - 3] = 0x00; + packet.m_body[offset - 4] = 0x00; + + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Video) { + p->OnRtmpFrame(packet.m_body + offset - 4, sps_len + 4); + } + } + } + // Write sps data + //fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr); + //fwrite(packet.m_body + offset, sizeof(uint8_t), sps_len, _file_ptr); + offset += sps_len; + } + uint32_t pps_num = packet.m_body[offset++] & 0x1f; + for (int i = 0; i < pps_num; i++) { + uint8_t ch0 = packet.m_body[offset]; + uint8_t ch1 = packet.m_body[offset + 1]; + uint32_t pps_len = ((ch0 << 8) | ch1); + offset += 2; + packet.m_body[offset - 1] = 0x01; + packet.m_body[offset - 2] = 0x00; + packet.m_body[offset - 3] = 0x00; + packet.m_body[offset - 4] = 0x00; + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Video) { + p->OnRtmpFrame(packet.m_body + offset - 4, pps_len + 4); + } + } + } + // Write pps data + offset += pps_len; + } + } + // Nalu frames + else { + uint32_t offset = 5; + uint8_t ch0 = packet.m_body[offset]; + uint8_t ch1 = packet.m_body[offset + 1]; + uint8_t ch2 = packet.m_body[offset + 2]; + uint8_t ch3 = packet.m_body[offset + 3]; + uint32_t data_len = ((ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3); + offset += 4; + packet.m_body[offset - 1] = 0x01; + packet.m_body[offset - 2] = 0x00; + packet.m_body[offset - 3] = 0x00; + packet.m_body[offset - 4] = 0x00; + + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Video) { + p->OnRtmpFrame(packet.m_body + offset - 4, data_len + 4); + } + } + } + // Write nalu data(already started with '0x00,0x00,0x00,0x01') + //fwrite(nalu_header, sizeof(uint8_t), 4, _file_ptr); + offset += data_len; + } + } + else if (packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) { + bool sequence = 0x00 == packet.m_body[1]; + printf("sequence=%s\n", sequence ? "true" : "false"); + // AAC sequence + if (sequence) { + uint8_t format = (packet.m_body[0] & 0xf0) >> 4; + uint8_t samplerate = (packet.m_body[0] & 0x0c) >> 2; + uint8_t sampledepth = (packet.m_body[0] & 0x02) >> 1; + uint8_t type = packet.m_body[0] & 0x01; + // sequence = packet.m_body[1]; + // AAC(AudioSpecificConfig) + if (format == 10) { + ch0 = packet.m_body[2]; + ch1 = packet.m_body[3]; + config = ((ch0 << 8) | ch1); + object_type = (config & 0xF800) >> 11; + sample_frequency_index = (config & 0x0780) >> 7; + channels = (config & 0x78) >> 3; + frame_length_flag = (config & 0x04) >> 2; + depend_on_core_coder = (config & 0x02) >> 1; + extension_flag = config & 0x01; + } + // Speex(Fix data here, so no need to parse...) + else if (format == 11) { + // 16 KHz, mono, 16bit/sample + type = 0; + sampledepth = 1; + samplerate = 4; + } + } + // Audio frames + else { + // ADTS(7 bytes) + AAC data + uint32_t data_len = packet.m_nBodySize - 2 + 7; + uint8_t adts[7]; + adts[0] = 0xff; + adts[1] = 0xf1; + adts[2] = ((object_type - 1) << 6) | (sample_frequency_index << 2) + | (channels >> 2); + adts[3] = ((channels & 3) << 6) + (data_len >> 11); + adts[4] = (data_len & 0x7FF) >> 3; + adts[5] = ((data_len & 7) << 5) + 0x1F; + adts[6] = 0xfc; + // Write audio frames + fwrite(adts, sizeof(uint8_t), 7, fp); + fwrite(packet.m_body + 2, sizeof(uint8_t), packet.m_nBodySize - 2, fp); + fflush(fp); + memcpy(mAccBuffer, adts, 7); + memcpy(mAccBuffer + 7, packet.m_body + 2, packet.m_nBodySize - 2); + + if (mObserver.size() > 0) { + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + RtmpPullObserver *p = (RtmpPullObserver *)*itr; + if (p->mObserverType == RtmpPullObserver::Observer_Audio) { + p->OnRtmpFrame(mAccBuffer, packet.m_nBodySize - 2 + 7); + } + } + } + } + } + RTMPPacket_Free(&packet); + } + return 0; +} + +int RtmpPuller2::SetObserver(RtmpPuller2::RtmpPullObserver *p) +{ + if (nullptr == p) + return -1; + mMux.lock(); + for (auto itr = this->mObserver.begin(); itr != mObserver.end(); itr++) { + if (p == *itr) return 0; + } + this->mObserver.push_back(p); + mMux.unlock(); + return 0; +} + +RtmpPuller2::CAP_STATUS RtmpPuller2::Status() +{ + return mStatus; +} + + +int RtmpPuller2::ConnectServer(string url) +{ + mRtmp = RTMP_Alloc(); + RTMP_Init(mRtmp); + if (RTMP_SetupURL(mRtmp, (char*)url.c_str()) == FALSE) + { + RTMP_Free(mRtmp); + mStatus = FAIL; + return -1; + } + /*ӷ*/ + if (RTMP_Connect(mRtmp, NULL) == FALSE) + { + RTMP_Free(mRtmp); + mStatus = FAIL; + return -1; + } + /**/ + if (RTMP_ConnectStream(mRtmp, 0) == FALSE) + { + RTMP_Close(mRtmp); + RTMP_Free(mRtmp); + mStatus = FAIL; + return -1; + } + mStatus = CONNECTED; + return 0; +} + + + diff --git a/client/qt_gl_render/yuvgl/media/RtmpPusher.cpp b/client/qt_gl_render/yuvgl/media/RtmpPusher.cpp new file mode 100644 index 0000000..db8bc56 --- /dev/null +++ b/client/qt_gl_render/yuvgl/media/RtmpPusher.cpp @@ -0,0 +1,570 @@ +#include "RtmpPusher.h" + +/** + * ʼwinsock + * + * @ɹ򷵻1 , ʧ򷵻Ӧ + */ +int InitSockets() +{ +#ifdef WIN32 + WORD version; + WSADATA wsaData; + version = MAKEWORD(1, 1); + return (WSAStartup(version, &wsaData) == 0); +#else + return TRUE; +#endif +} + +bool RtmpPusher::IfConnect() +{ + return mIfConnected; +} + +int RtmpPusher::RTMP264_Connect(const char* url) +{ + InitSockets(); + m_pRtmp = RTMP_Alloc(); + RTMP_Init(m_pRtmp); + /*URL*/ + if (RTMP_SetupURL(m_pRtmp, (char*)url) == FALSE) + { + RTMP_Free(m_pRtmp); + return -1; + } + /*ÿд,,ǰʹ,Ч*/ + RTMP_EnableWrite(m_pRtmp); + /*ӷ*/ + if (RTMP_Connect(m_pRtmp, NULL) == FALSE) + { + RTMP_Free(m_pRtmp); + return -1; + } + + /**/ + if (RTMP_ConnectStream(m_pRtmp, 0) == FALSE) + { + RTMP_Close(m_pRtmp); + RTMP_Free(m_pRtmp); + return -1; + } + + this->mUrl = string(url); + this->mIfConnected = true; + return 0; +} +/** + * ͷwinsock + * + * @ɹ򷵻0 , ʧ򷵻Ӧ + */ +inline void CleanupSockets() +{ +#ifdef WIN32 + WSACleanup(); +#endif +} + +void RtmpPusher::RTMP264_Close() +{ + mMux.lock(); + if (m_pRtmp) + { + RTMP_Close(m_pRtmp); + RTMP_Free(m_pRtmp); + m_pRtmp = NULL; + } + mMux.unlock(); + CleanupSockets(); +} +RTMPPacket* gPacket = nullptr; + +int RtmpPusher::SendPacket(unsigned int nPacketType, unsigned char * data, + unsigned int size, unsigned int nTimestamp) +{ + static bool once = true; + /*ڴͳʼ,lenΪ峤*/ + if(nullptr == gPacket) + gPacket = (RTMPPacket *)malloc(640*720*3 + size); + memset(gPacket, 0, RTMP_HEAD_SIZE); + /*ڴ*/ + + gPacket->m_body = (char *)gPacket + RTMP_HEAD_SIZE; + gPacket->m_nBodySize = size; + memcpy(gPacket->m_body, data, size); + + gPacket->m_hasAbsTimestamp = 0; + gPacket->m_packetType = nPacketType; /*˴ΪһƵ,һƵ*/ + gPacket->m_nInfoField2 = m_pRtmp->m_stream_id; + gPacket->m_nChannel = 0x04; + + + gPacket->m_headerType = RTMP_PACKET_SIZE_LARGE; + if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size != 4) + { + gPacket->m_headerType = RTMP_PACKET_SIZE_MEDIUM; + } + + gPacket->m_nTimeStamp = nTimestamp; + /**/ + int nRet = 0; + if (RTMP_IsConnected(m_pRtmp)) + { + nRet = RTMP_SendPacket(m_pRtmp, gPacket, FALSE); /*TRUEΪŽͶ,FALSEDzŽͶ,ֱӷ*/ + } + else { + if (once) { + once = false; + std::cout<<"rtmp Ͽ at "<<__FILE__<<" line "<<__LINE__; + } + } + /*ͷڴ*/ + //free(gPacket); + return nRet; +} + +int RtmpPusher::SendVideoPacket(unsigned int nPacketType, + unsigned char * data, unsigned int size, unsigned int nTimestamp) +{ + + RTMPPacket* packet; + /*ڴͳʼ,lenΪ峤*/ + packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + size); + memset(packet, 0, RTMP_HEAD_SIZE); + /*ڴ*/ + packet->m_body = (char *)packet + RTMP_HEAD_SIZE; + packet->m_nBodySize = size; + memcpy(packet->m_body, data, size); + packet->m_hasAbsTimestamp = 0; + packet->m_packetType = nPacketType; /*˴ΪһƵ,һƵ*/ + packet->m_nInfoField2 = m_pRtmp->m_stream_id; + packet->m_nChannel = 0x04; + + packet->m_headerType = RTMP_PACKET_SIZE_LARGE; + if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size != 4) + { + packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; + } + packet->m_nTimeStamp = nTimestamp; + /**/ + int nRet = 0; + if (RTMP_IsConnected(m_pRtmp)) + { + nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE); /*TRUEΪŽͶ,FALSEDzŽͶ,ֱӷ*/ + } + /*ͷڴ*/ + free(packet); + return 0; +} + +RtmpPusher::RtmpPusher() + :mThread(nullptr), + mIfConnected(false) +{ + +} + + +RtmpPusher::~RtmpPusher() +{ + if (m_pRtmp) + { + RTMP_Close(m_pRtmp); + RTMP_Free(m_pRtmp); + m_pRtmp = NULL; + } + CleanupSockets(); +} + +void H264RtmpPuser::OnAudioEncode(const void * frameaddress, uint32_t framelen,uint16_t pts) +{ + uint8_t *pack = (uint8_t*)malloc(framelen); + memcpy(pack, frameaddress, framelen); + + mMux.lock(); + Buffer buf; + buf.buf = (uint8_t *)pack; + buf.len = framelen; + buf.type = PAYLOAD_TYPE_AUDIO; + this->mPack.push(buf); + mMux.unlock(); + this->mAudioPts = pts; +} + +H264RtmpPuser::H264RtmpPuser() +{ + this->metaData.Pps = nullptr; + this->metaData.Sps = nullptr; + this->metaData.nPpsLen = 0; + this->metaData.nSpsLen = 0; + mFirtACC = false; +} + +int H264RtmpPuser::sortAndSendNal(uint8_t * data, int len) +{ + int i = 0; + uint8_t * nalhead = nullptr; + uint8_t * naltail = nullptr; + uint32_t size = 0; + + static int timestamp = 0; + timestamp += 1000 / 25; + if (nullptr == data) { + return -1; + } + while (i < len) + { + // sps pps p frame + if ((data[i] == 0x00) && (data[i + 1] == 0x00) + && ((data[i + 2] == 0x00) && (data[i + 3] == 0x01) || (data[i + 2] == 0x01))) { + if ((nalhead == nullptr) && (i == 0) ) { + if ((data[i + 3] == 0x01) && (data[i + 4] == 0x41)) { //p ֱ֡ӷ + nalhead = data; + naltail = data + (len); + size = naltail - nalhead; + this->SendH264Packet(nalhead, size, 0, timestamp); + return 0; + } + //sps ֡н + if ((data[i + 3] == 0x01) && (data[i + 4] == 0x67)) { // sps or pps or sei + nalhead = data; + i += 1; + } + //sei + if ((data[i + 2] == 0x01) && (data[i + 3] == 0x06)) { + i += 1; + } + } + else { + // i frame + if ((data[i + 2] == 0x01) && (data[i + 3] == 0x65)) { + naltail = data + i; + size = naltail - nalhead; + this->SendH264Packet(nalhead, size, 0, timestamp); + nalhead = data + i; + naltail = data + (len); + size = naltail - nalhead; + this->SendH264Packet(nalhead, size, 0, timestamp); + return 0; + } + //pps + if ((data[i + 3] == 0x01) && (data[i + 4] == 0x68)) { // sps or pps or sei + naltail = data + i; + size = naltail - nalhead; + this->SendH264Packet(nalhead, size, 0, timestamp); + nalhead = data + i; + i += 3; + }//sps + if ((data[i + 3] == 0x01) && (data[i + 4] == 0x67)) { // sps or pps or sei + nalhead = data + i; + i += 3; + } + //sei + if ((data[i + 3] == 0x01) && (data[i + 4] == 0x06)) { // sps or pps or sei + naltail = data + i; + size = naltail - nalhead; + this->SendH264Packet(nalhead, size, 0, timestamp); + nalhead = data + i; + i += 3; + } + // sps pps or sei + } + // 00 00 00 00 01 + } + i++; + } + return 0; +} + + +// Ƶͬϸṹhttps://blog.csdn.net/liwf616/article/details/51596373 +int H264RtmpPuser::SendVideoSpsPps(unsigned char * pps, + int pps_len, unsigned char * sps, + int sps_len,unsigned int nTimeStamp) +{ + RTMPPacket * packet = NULL;//rtmpṹ + unsigned char * body = NULL; + int i; + packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024); + //RTMPPacket_Reset(packet);//packet״̬ + memset(packet, 0, RTMP_HEAD_SIZE + 1024); + packet->m_body = (char *)packet + RTMP_HEAD_SIZE; + body = (unsigned char *)packet->m_body; + i = 0; + // FrameType == 1CodecID == 7 + body[i++] = 0x17; + //AVCPacketType + body[i++] = 0x00; + + //CompositionTime + body[i++] = 0x00; + body[i++] = 0x00; + body[i++] = 0x00; + + /*AVCDecoderConfigurationRecord*/ + body[i++] = 0x01; + body[i++] = sps[1]; + body[i++] = sps[2]; + body[i++] = sps[3]; + body[i++] = 0xff; + + /*sps*/ + body[i++] = 0xe1; + body[i++] = (sps_len >> 8) & 0xff; + body[i++] = sps_len & 0xff; + memcpy(&body[i], sps, sps_len); + i += sps_len; + + /*pps*/ + body[i++] = 0x01; + body[i++] = (pps_len >> 8) & 0xff; + body[i++] = (pps_len) & 0xff; + memcpy(&body[i], pps, pps_len); + i += pps_len; + + packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; + packet->m_nBodySize = i; + packet->m_nChannel = 0x04; + packet->m_nTimeStamp = nTimeStamp; + packet->m_hasAbsTimestamp = 0; + packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; + packet->m_nInfoField2 = m_pRtmp->m_stream_id; + + /*÷ͽӿ*/ + int nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE); + free(packet); //ͷڴ + return nRet; +} + +int H264RtmpPuser::SendAudioData(unsigned char * dat, + unsigned int size, unsigned int nTimeStamp) +{ + return 0; +} + +int H264RtmpPuser::SendH264Packet(unsigned char * data, + unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp) +{ + if(data == NULL){ + return false; + } + unsigned int nal_type = 0; + // С֡ӦPPSSPS + if ((data[0] != 0x00) || (data[1] != 0x00) + || ((data[2] != 0x00)&&data[2]!= 0x01)) { + return false; + } + //Debuger::Debug(L"%02x %02x %02x %02x %02x %02d\r\n", + // data[0],data[1],data[2],data[3],data[4],size); + if (data[2] == 0x01) { + nal_type = data[3]; + } + if (data[3] == 0x01) { + nal_type = data[4]; + } + switch (nal_type) + { + case 0x67: //just update sps and pps + if (NULL == metaData.Sps) + metaData.Sps = (unsigned char *)malloc(size - 4); + h264_decode_sps(data + 4, size - 4, metaData.nWidth, metaData.nHeight, metaData.nFrameRate); + metaData.nSpsLen = size - 4; + memcpy(this->metaData.Sps, data + 4, size - 4); + break; + case 0x68: //just update sps and pps + this->metaData.nPpsLen = size - 4; + if (NULL == metaData.Pps) metaData.Pps = (unsigned char *)malloc(size - 4); + memcpy(this->metaData.Pps, data + 4, size - 4); + break; + case 0x41: //p frame + this->sendDataPackH264(data + 4, size - 4, 0, nTimeStamp); + break; + case 0x65: //i frame + this->sendDataPackH264(data + 3, size - 3, 1, nTimeStamp); + break; + case 0x06: + size = size; + //this->sendDataPack(data + 4, size - 4, 0, nTimeStamp); + break; + default: + break; + } + +} +unsigned char *gBody = nullptr; +int H264RtmpPuser::sendDataPackH264(unsigned char * data, + unsigned int size, int bIsKeyFrame, unsigned int nTimeStamp) +{ + if (gBody == nullptr) { + gBody = new unsigned char[640*720*3 + 9]; + } + if (size < 0) { + gBody = gBody; + } + memset(gBody, 0, size + 9); + int i = 0; + if (1 == bIsKeyFrame) { + gBody[i++] = 0x17;// 1:Iframe 7:AVC + gBody[i++] = 0x01;// AVC NALU + gBody[i++] = 0x00; + gBody[i++] = 0x00; + gBody[i++] = 0x00; + + // NALU size + gBody[i++] = size >> 24 & 0xff; + gBody[i++] = size >> 16 & 0xff; + gBody[i++] = size >> 8 & 0xff; + gBody[i++] = size & 0xff; + // NALU data + memcpy(&gBody[i], data, size); + if(metaData.Sps != nullptr) + SendVideoSpsPps(metaData.Pps, metaData.nPpsLen, metaData.Sps, + metaData.nSpsLen, nTimeStamp); + } + else { + gBody[i++] = 0x27;// 2:Pframe 7:AVC + gBody[i++] = 0x01;// AVC NALU + gBody[i++] = 0x00; + gBody[i++] = 0x00; + gBody[i++] = 0x00; + // NALU size + gBody[i++] = size >> 24 & 0xff; + gBody[i++] = size >> 16 & 0xff; + gBody[i++] = size >> 8 & 0xff; + gBody[i++] = size & 0xff; + // NALU data + + memcpy(&gBody[i], data, size); + } + int bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO, gBody, i + size, nTimeStamp); + return bRet; +} + + +int H264RtmpPuser::SendAudioSync(int audioType, + int sampleIndex, int channel, unsigned int nTimeStamp) +{ + RTMPPacket * packet = NULL;//rtmpṹ + unsigned char * body = NULL; + int i; + + packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024); + //RTMPPacket_Reset(packet);//packet״̬ + memset(packet, 0, RTMP_HEAD_SIZE + 1024); + packet->m_body = (char *)packet + RTMP_HEAD_SIZE; + body = (unsigned char *)packet->m_body; + + body[0] = 0xaf; + body[1] = 0x00; + + uint16_t audioSpecConf = 0; + audioSpecConf |= ((2 << 11) & 0xf800); //2: AACLC + audioSpecConf |= ((4 << 7) & 0x0780); //4: 44khz + audioSpecConf |= ((2 << 3) & 0x78); //4: 2:stero + audioSpecConf |= 0 & 0x07; //4: 0 padding + body[2] = (audioSpecConf >> 8) & 0xff; + body[3] = audioSpecConf & 0xff; + + + packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; + packet->m_nBodySize = 4; + packet->m_nChannel = 0x04; + packet->m_nTimeStamp = nTimeStamp; + packet->m_hasAbsTimestamp = 0; + packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; + packet->m_nInfoField2 = m_pRtmp->m_stream_id; + + /*÷ͽӿ*/ + int nRet = RTMP_SendPacket(m_pRtmp, packet, TRUE); + free(packet); //ͷڴ + return nRet; +} + + +int H264RtmpPuser::sendDataPackAAC(unsigned char * data, + unsigned int size, unsigned int nTimeStamp) +{ + unsigned char *gBody = nullptr; + static int timestamp = 0; + timestamp += 20; + if (!mFirtACC) { + SendAudioSync(2,4,4, timestamp); + mFirtACC = 1; + } + gBody = (unsigned char*)malloc(size + 2); + gBody[0] = 0xAF; + gBody[1] = 0x01; //aac raw data + memcpy(gBody + 2, data + 7, size - 7); + int bRet = SendPacket(RTMP_PACKET_TYPE_AUDIO, gBody, + size - 7 + 2, timestamp); + free(gBody); + return 0; +} + +void H264RtmpPuser::OnGetCodeFrame(uint8_t * data, int len) +{ + static int timetamp = 0; + timetamp += this->mTick; + uint8_t *pack = (uint8_t*)malloc(len); + memcpy(pack, data, len); + mMux.lock(); + Buffer buf; + buf.buf = pack; + buf.len = len; + buf.type = PAYLOAD_TYPE_VIDEO; + this->mPack.push(buf); + mMux.unlock(); +} + +void H264RtmpPuser::ProcessSend() +{ + while (this->mIfStart) { + int len = mPack.size(); + if (!mPack.empty()) { + mMux.lock(); + Buffer buf = mPack.front(); + mPack.pop(); + mMux.unlock(); + //Ƶ֡ + if (buf.type == PAYLOAD_TYPE_VIDEO) { + this->sortAndSendNal(buf.buf, buf.len); + + }// Ƶ֡ + if (buf.type == PAYLOAD_TYPE_AUDIO) { + this->sendDataPackAAC(buf.buf, buf.len, this->mAudioPts); + } + free(buf.buf); + } + msleep(10); + } +} + +int ThreadEncode(H264RtmpPuser * p) +{ + Debuger::Debug(L"thread started\r\n"); + if (nullptr == p) + return -1; + p->ProcessSend(); + return 0; +} + +int H264RtmpPuser::StartPush() +{ + mIfStart = true; + this->mThread = new std::thread(ThreadEncode,this); + mThreadId = this->mThread->get_id(); + return 0; +} + +int H264RtmpPuser::StopPush() +{ + mIfConnected = false; + mIfStart = false; + if(mThread != nullptr) + this->mThread->join(); + this->RTMP264_Close(); + return 0; +} + + diff --git a/client/qt_gl_render/yuvgl/media/RtmpPusher.h b/client/qt_gl_render/yuvgl/media/RtmpPusher.h index b320ed0..cf3356c 100644 --- a/client/qt_gl_render/yuvgl/media/RtmpPusher.h +++ b/client/qt_gl_render/yuvgl/media/RtmpPusher.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef WIN32 #include diff --git a/client/qt_gl_render/yuvgl/yuvgl.pro b/client/qt_gl_render/yuvgl/yuvgl.pro index 50f8bdd..0b43eed 100644 --- a/client/qt_gl_render/yuvgl/yuvgl.pro +++ b/client/qt_gl_render/yuvgl/yuvgl.pro @@ -32,6 +32,9 @@ SOURCES += \ cplaywidget.cpp \ media/AACAudioCoder.cpp \ media/CameraCapture.cpp \ + media/RtmpPuller.cpp \ + media/RtmpPuller2.cpp \ + media/RtmpPusher.cpp \ media/imgutil.cpp \ utils/Base64.cpp \ utils/Debuger.cpp \ diff --git a/client/qt_gl_render/yuvgl/yuvgl.pro.user b/client/qt_gl_render/yuvgl/yuvgl.pro.user new file mode 100644 index 0000000..1d1ade5 --- /dev/null +++ b/client/qt_gl_render/yuvgl/yuvgl.pro.user @@ -0,0 +1,1291 @@ + + + + + + EnvironmentId + {bccf4b57-4470-414a-a840-fdad9939972c} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + -fno-delayed-template-parsing + + true + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.14.0 MinGW 32-bit + Desktop Qt 5.14.0 MinGW 32-bit + qt.qt5.5140.win32_mingw73_kit + 0 + 0 + 0 + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_32_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + + false + + false + true + true + false + false + true + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_32_bit-Debug + + 1 + + + + ProjectExplorer.Project.Target.1 + + Qt 5.14.0 (msvc2017) + Qt 5.14.0 (msvc2017) + {e7f4d0a4-ebba-4430-a5f7-020932213e78} + 0 + 0 + 0 + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_e7f4d0-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_e7f4d0-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_e7f4d0-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + + false + + false + true + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.Target.2 + + Qt 5.14.0 (msvc2017) + Qt 5.14.0 (msvc2017) + {64b425c2-38f7-4c05-8b08-ba417625d345} + 0 + 0 + 0 + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_64b425-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_64b425-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Qt_5_14_0_msvc2017_64b425-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + + ProjectExplorer.CustomExecutableRunConfiguration + + + false + + false + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.Target.3 + + Desktop Qt 5.14.0 MinGW 64-bit + Desktop Qt 5.14.0 MinGW 64-bit + qt.qt5.5140.win64_mingw73_kit + 0 + 0 + 0 + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_64_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_64_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MinGW_64_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + + false + + false + true + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.Target.4 + + Desktop Qt 5.14.0 MSVC2017 64bit + Desktop Qt 5.14.0 MSVC2017 64bit + qt.qt5.5140.win64_msvc2017_64_kit + 0 + 0 + 0 + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MSVC2017_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MSVC2017_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + D:/project/multimedia/client/qt_gl_render/build-yuvgl-Desktop_Qt_5_14_0_MSVC2017_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + D:/project/multimedia/client/qt_gl_render/yuvgl/yuvgl.pro + + false + + false + true + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.TargetCount + 5 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/device/mpp/sample/hisi_rtsp_demo-master/rtsp_lib/rtsp.log b/device/mpp/sample/hisi_rtsp_demo-master/rtsp_lib/rtsp.log new file mode 100644 index 0000000..61483fc --- /dev/null +++ b/device/mpp/sample/hisi_rtsp_demo-master/rtsp_lib/rtsp.log @@ -0,0 +1,97 @@ +OPTIONS rtsp://192.168.6.68 RTSP/1.0 +CSeq: 2 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) + +RTSP/1.0 200 OK +CSeq: 2 +Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER +Date: Fri, Dec 25 2015 14:08:41 GMT + +DESCRIBE rtsp://192.168.6.68 RTSP/1.0 +CSeq: 3 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Accept: application/sdp + +RTSP/1.0 200 OK +CSeq: 3 +Content-Type: application/sdp +Content-Base: rtsp://192.168.6.68/ +Content-Length: 679 + +v=0 +o=- 1451052521156494 1451052521156494 IN IP4 192.168.6.68 +s=Media Presentation +e=NONE +b=AS:5100 +t=0 0 +a=control:rtsp://192.168.6.68/ +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:5000 +a=recvonly +a=x-dimensions:1280,960 +a=control:rtsp://192.168.6.68/trackID=1 +a=rtpmap:96 H264/90000 +a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z2QAIK2EAQwgCGEAQwgCGEAQwgCEK1AoA803AQEBAg==,aO48sA== +m=audio 0 RTP/AVP 0 +c=IN IP4 0.0.0.0 +b=AS:50 +a=recvonly +a=control:rtsp://192.168.6.68/trackID=2 +a=rtpmap:0 PCMU/8000 +a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000; +a=appversion:1.0 +SETUP rtsp://192.168.6.68/trackID=1 RTSP/1.0 +CSeq: 4 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Transport: RTP/AVP;unicast;client_port=56296-56297 + +RTSP/1.0 200 OK +CSeq: 4 +Session: 283812775;timeout=60 +Transport: RTP/AVP;unicast;client_port=56296-56297;server_port=8236-8237;ssrc=2d02fa0f;mode="play" +Date: Fri, Dec 25 2015 14:08:41 GMT + +SETUP rtsp://192.168.6.68/trackID=2 RTSP/1.0 +CSeq: 5 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Transport: RTP/AVP;unicast;client_port=56298-56299 +Session: 283812775 + +RTSP/1.0 200 OK +CSeq: 5 +Session: 283812775;timeout=60 +Transport: RTP/AVP;unicast;client_port=56298-56299;server_port=8234-8235;ssrc=1266baad;mode="play" +Date: Fri, Dec 25 2015 14:08:41 GMT + +PLAY rtsp://192.168.6.68/ RTSP/1.0 +CSeq: 6 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Session: 283812775 +Range: npt=0.000- + +RTSP/1.0 200 OK +CSeq: 6 +Session: 283812775 +RTP-Info: url=rtsp://192.168.6.68/trackID=1;seq=16387;rtptime=1724325370,url=rtsp://192.168.6.68/trackID=2;seq=30619;rtptime=153273528 +Date: Fri, Dec 25 2015 14:08:41 GMT + +GET_PARAMETER rtsp://192.168.6.68/ RTSP/1.0 +CSeq: 7 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Session: 283812775 + +RTSP/1.0 200 OK +CSeq: 7 +Date: Fri, Dec 25 2015 14:08:41 GMT + +TEARDOWN rtsp://192.168.6.68/ RTSP/1.0 +CSeq: 8 +User-Agent: LibVLC/2.1.5 (LIVE555 Streaming Media v2014.05.27) +Session: 283812775 + +RTSP/1.0 200 OK +CSeq: 8 +Session: 283812775 +Date: Fri, Dec 25 2015 14:08:49 GMT + diff --git a/device/mpp/sample/venc/a.exe b/device/mpp/sample/venc/a.exe new file mode 100644 index 0000000000000000000000000000000000000000..f480a3395ca8438de87aac158a057f816419ecbd GIT binary patch literal 61568 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 literal 0 HcmV?d00001