【RTSP從零實踐】3、實現最簡單的傳輸H264的RTSP服務器

😁博客主頁😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客內容🤑:🍭嵌入式開發、Linux、C語言、C++、數據結構、音視頻🍭
🤣本文內容🤣:🍭介紹怎么實現最簡單的傳輸H264的RTSP服務器🍭
😎金句分享😎:🍭你不能選擇最好的,但最好的會來選擇你——泰戈爾🍭
?發布時間?: 2025-06-30

本文未經允許,不得轉發!!!

目錄

  • 🎄一、概述
  • 🎄二、實現步驟、實現細節
    • ?2.1、使用RTSP服務的TCP套接字并監聽
    • ?2.2、接收客戶端請求并處理RTSP命令
    • ?2.3、讀取H264并發送
  • 🎄三、傳輸H264的RTSP服務器的實現源碼
  • 🎄四、總結


在這里插入圖片描述

RTSP從零實踐系列文章回顧:
【RTSP從零實踐】1、根據RTSP協議實現一個RTSP服務
【RTSP從零實踐】2、使用RTP協議封裝并傳輸H264
【RTSP從零實踐】3、實現最簡單的傳輸H264的RTSP服務器
【RTSP從零實踐】4、使用RTP協議封裝并傳輸AAC

在這里插入圖片描述

🎄一、概述

這篇文章主要就是介紹怎么通過RTSP協議通信,將H264視頻數據封裝成RTP包并發送的,目的是寫一個最簡單的RTSP服務器,熟悉RTSP協議處理、RTP協議封包。

包含了下面知識點:
1、RTSP協議的內容;
2、實現TCP協議實現RTSP;
3、RTP協議;
4、怎么實現RTP協議;

這些知識點都可以從上面 【RTSP從零實踐】系列文章學習到,這篇文章不再贅述,只介紹步驟和代碼。


在這里插入圖片描述

🎄二、實現步驟、實現細節

這個小寫介紹怎么實現的步驟、細節,下個小節會提供源碼,可以結合著源碼看幫助理解消化。

?2.1、使用RTSP服務的TCP套接字并監聽

RTSP協議是基于TCP作為傳輸層協議去實現的。

// 創建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{perror("socket failed");return -1;
}// 設置套接字選項
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
{perror("setsockopt");return -1;
}address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(RTSP_PORT);// 綁定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
{perror("bind failed");return -1;
}// 開始監聽
if (listen(server_fd, MAX_CLIENTS) < 0)
{perror("listen");return -1;
}

?2.2、接收客戶端請求并處理RTSP命令

RTSP通信不直接傳輸音視頻碼流,會先進行一些命令通信,所以第二個步驟就是處理這些命令:

char response[1024] = {0}; // 構造響應
if (strcmp(method, "OPTIONS") == 0)
{rtsp_handle_OPTION(response, cseq);
}
else if (strcmp(method, "DESCRIBE") == 0)
{rtsp_handle_DESCRIBE(response, cseq);
}
else if (strcmp(method, "SETUP") == 0)
{rtsp_handle_SETUP(response, cseq, rtpPort);
}
else if (strcmp(method, "PLAY") == 0)
{rtsp_handle_PLAY(response, cseq);bSendFlag = RTP_PLAY;
}
else if (strcmp(method, "TEARDOWN") == 0)
{rtsp_handle_TEARDOWN(response, cseq);bSendFlag = RTP_STOP;
}
else
{snprintf(response, sizeof(response),"RTSP/1.0 501 Not Implemented\r\nCSeq: %d\r\n\r\n", cseq);
}

?2.3、讀取H264并發送

處理完RTSP命令后,就需要讀取并發送RTP包了。

// h264
H264ReaderInfo_t h264Info;
if (H264_FileOpen(H264_FILE_NAME, &h264Info) < 0)
{printf("failed to open %s\n", H264_FILE_NAME);return NULL;
}H264Frame_t h264Frame;
h264Frame.pFrameBuf = (unsigned char *)malloc(1920 * 1080 * 4);while (pRtpSend->bPlayFlag)
{if (!H264_IsEndOfFile(&h264Info)){h264Frame.isLastFrame = 0;H264_GetFrame(&h264Frame, &h264Info);if (h264Frame.pFrameBuf != NULL){if (h264Frame.isLastFrame) // 最后一幀,移到開頭重新讀{printf("warning SeekFile 1\n");H264_SeekFile(&h264Info);}// printf("rtpSendH264Frame, frameNum=%d, time=%u\n", h264Info.frameNum, rtpPacket->rtpHeader.timestamp);rtpSendH264Frame(rtp_send_fd, cli_ip, rtpPort, rtpPacket,h264Frame.pFrameBuf + h264Frame.startcode_len,h264Frame.frame_len - h264Frame.startcode_len);rtpPacket->rtpHeader.timestamp += 90000 / FPS; // RTP 傳輸視頻每秒 90k HZusleep(1000 * 1000 / FPS);}}else{printf("warning need SeekFile 1\n");}
}

在這里插入圖片描述

🎄三、傳輸H264的RTSP服務器的實現源碼

總共有5個源代碼文件,具體如下:

1、H264Reader.h

/*** @file H264Reader.h* @author https://blog.csdn.net/wkd_007* @brief* @version 0.1* @date 2025-06-24** @copyright Copyright (c) 2025**/
#ifndef __H264_READER_H__
#define __H264_READER_H__#include <stdio.h>#define MAX_STARTCODE_LEN (4)typedef enum
{FALSE,TRUE,
} BOOL;typedef enum
{H264_NALU_TYPE_SLICE = 1,H264_NALU_TYPE_DPA = 2,H264_NALU_TYPE_DPB = 3,H264_NALU_TYPE_DPC = 4,H264_NALU_TYPE_IDR = 5,H264_NALU_TYPE_SEI = 6,H264_NALU_TYPE_SPS = 7,H264_NALU_TYPE_PPS = 8,H264_NALU_TYPE_AUD = 9,H264_NALU_TYPE_EOSEQ = 10,H264_NALU_TYPE_EOSTREAM = 11,H264_NALU_TYPE_FILL = 12,
} H264NaluType;typedef enum
{H264_NALU_PRIORITY_DISPOSABLE = 0,H264_NALU_PRIRITY_LOW = 1,H264_NALU_PRIORITY_HIGH = 2,H264_NALU_PRIORITY_HIGHEST = 3
} H264NaluPriority;typedef struct
{int startcode_len;        //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)int forbidden_bit;        //! should be always FALSEint nal_reference_idc;    //! H264_NALU_PRIORITY_xxxxint nal_unit_type;        //! H264_NALU_TYPE_xxxxBOOL isLastFrame;         //!int frame_len;            //!unsigned char *pFrameBuf; //!
} H264Frame_t;typedef struct H264ReaderInfo_s
{FILE *pFileFd;int frameNum;
} H264ReaderInfo_t;int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info);
int H264_FileClose(H264ReaderInfo_t *pH264Info);
int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info);
BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info);
void H264_SeekFile(H264ReaderInfo_t *pH264Info);#endif // __H264_READER_H__

2、H264Reader.c

/*** @file H264Reader.c* @author https://blog.csdn.net/wkd_007* @brief* @version 0.1* @date 2025-06-30** @copyright Copyright (c) 2025**/
#include "H264Reader.h"
#include <stdlib.h>#define MAX_FRAME_LEN (1920 * 1080 * 1.5) // 一幀數據最大字節數static BOOL findStartCode_001(unsigned char *Buf)
{// printf("[%d %d %d]\n", Buf[0], Buf[1], Buf[2]);return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 1); // 0x000001?
}static BOOL findStartCode_0001(unsigned char *Buf)
{// printf("[%d %d %d %d]\n", Buf[0], Buf[1], Buf[2], Buf[3]);return (Buf[0] == 0 && Buf[1] == 0 && Buf[2] == 0 && Buf[3] == 1); // 0x00000001?
}int H264_FileOpen(char *fileName, H264ReaderInfo_t *pH264Info)
{pH264Info->pFileFd = fopen(fileName, "rb+");if (pH264Info->pFileFd == NULL){printf("[%s %d]Open file error\n", __FILE__, __LINE__);return -1;}pH264Info->frameNum = 0;return 0;
}int H264_FileClose(H264ReaderInfo_t *pH264Info)
{if (pH264Info->pFileFd != NULL){fclose(pH264Info->pFileFd);pH264Info->pFileFd = NULL;}return 0;
}BOOL H264_IsEndOfFile(const H264ReaderInfo_t *pH264Info)
{return feof(pH264Info->pFileFd);
}void H264_SeekFile(H264ReaderInfo_t *pH264Info)
{fseek(pH264Info->pFileFd, 0, SEEK_SET);pH264Info->frameNum = 0;
}/*** @brief 獲取一陣h264視頻幀** @param pH264Frame :輸出參數,使用后 pH264Frame->pFrameBuf 需要free* @param pH264Info :輸入參數* @return int*/
int H264_GetFrame(H264Frame_t *pH264Frame, H264ReaderInfo_t *pH264Info)
{int rewind = 0;if (pH264Info->pFileFd == NULL){printf("[%s %d]pFileFd error\n", __FILE__, __LINE__);return -1;}// 1.讀取幀數據// unsigned char *pFrame = (unsigned char *)malloc(MAX_FRAME_LEN);unsigned char *pFrame = pH264Frame->pFrameBuf;int readLen = fread(pFrame, 1, MAX_FRAME_LEN, pH264Info->pFileFd);if (readLen <= 0){printf("[%s %d]fread error\n", __FILE__, __LINE__);// free(pFrame);return -1;}// 2.查找當前幀開始碼int i = 0;for (; i < readLen - MAX_STARTCODE_LEN; i++){if (!findStartCode_0001(&pFrame[i])){if (!findStartCode_001(&pFrame[i])){continue;}else{pH264Frame->startcode_len = 3;break;}}else{pH264Frame->startcode_len = 4;break;}}if (i != 0) // 不是幀開頭,偏移到幀開頭重新讀{printf("[%s %d]startcode error, i=%d\n", __FILE__, __LINE__, i);// free(pFrame);rewind = (-(readLen - i));fseek(pH264Info->pFileFd, rewind, SEEK_CUR);return -1;}// 3.查找下一幀開始碼i += MAX_STARTCODE_LEN;for (; i < readLen - MAX_STARTCODE_LEN; i++){if (!findStartCode_0001(&pFrame[i])){if (!findStartCode_001(&pFrame[i])){continue;}else{break;}}else{break;}}if (i == (readLen - MAX_STARTCODE_LEN)){if (!feof(pH264Info->pFileFd)){printf("[%s %d]MAX_FRAME_LEN too small\n", __FILE__, __LINE__);// free(pFrame);return -1;}else{pH264Frame->isLastFrame = TRUE;}}// 4.填數據pH264Frame->forbidden_bit = pFrame[pH264Frame->startcode_len] & 0x80;     // 1 bitpH264Frame->nal_reference_idc = pFrame[pH264Frame->startcode_len] & 0x60; // 2 bitpH264Frame->nal_unit_type = pFrame[pH264Frame->startcode_len] & 0x1f;     // 5 bit, naluType 是開始碼后一個字節的最后 5 位// pH264Frame->pFrameBuf = pFrame;pH264Frame->frame_len = i;// 5.文件讀取指針偏移到下一幀位置rewind = (-(readLen - i));fseek(pH264Info->pFileFd, rewind, SEEK_CUR);pH264Info->frameNum++;return pH264Frame->frame_len;
}

3、rtp.h

#ifndef _RTP_H_
#define _RTP_H_
#include <stdint.h>#define RTP_VESION              2#define RTP_PAYLOAD_TYPE_H264   96
#define RTP_PAYLOAD_TYPE_AAC    97#define RTP_HEADER_SIZE         12
#define RTP_MAX_PKT_SIZE        1400/***    0                   1                   2                   3*    7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |V=2|P|X|  CC   |M|     PT      |       sequence number         |*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |                           timestamp                           |*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*   |           synchronization source (SSRC) identifier            |*   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+*   |            contributing source (CSRC) identifiers             |*   :                             ....                              :*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+**/
struct RtpHeader
{/* byte 0 */uint8_t csrcLen:4;uint8_t extension:1;uint8_t padding:1;uint8_t version:2;/* byte 1 */uint8_t payloadType:7;uint8_t marker:1;/* bytes 2,3 */uint16_t seq;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc;
};struct RtpPacket
{struct RtpHeader rtpHeader;uint8_t payload[0];
};void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc);
int rtpSendPacket(int socket, char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize);#endif //_RTP_H_

4、rtp.c

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "rtp.h"void rtpHeaderInit(struct RtpPacket *rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc)
{rtpPacket->rtpHeader.csrcLen = csrcLen;rtpPacket->rtpHeader.extension = extension;rtpPacket->rtpHeader.padding = padding;rtpPacket->rtpHeader.version = version;rtpPacket->rtpHeader.payloadType = payloadType;rtpPacket->rtpHeader.marker = marker;rtpPacket->rtpHeader.seq = seq;rtpPacket->rtpHeader.timestamp = timestamp;rtpPacket->rtpHeader.ssrc = ssrc;
}int rtpSendPacket(int socket, char *ip, int16_t port, struct RtpPacket *rtpPacket, uint32_t dataSize)
{struct sockaddr_in addr;int ret;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);rtpPacket->rtpHeader.seq = htons(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = htonl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = htonl(rtpPacket->rtpHeader.ssrc);ret = sendto(socket, (void *)rtpPacket, dataSize + RTP_HEADER_SIZE, 0,(struct sockaddr *)&addr, sizeof(addr));rtpPacket->rtpHeader.seq = ntohs(rtpPacket->rtpHeader.seq);rtpPacket->rtpHeader.timestamp = ntohl(rtpPacket->rtpHeader.timestamp);rtpPacket->rtpHeader.ssrc = ntohl(rtpPacket->rtpHeader.ssrc);return ret;
}

5、rtsp_h264_main.c

/*** @file rtsp_h264_main.c* @author https://blog.csdn.net/wkd_007* @brief* @version 0.1* @date 2025-06-30** @copyright Copyright (c) 2025**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>#include "rtp.h"
#include "H264Reader.h"#define H264_FILE_NAME "test.h264"
#define FPS 25#define RTSP_PORT 8554
#define RTP_PORT 55666
#define MAX_CLIENTS 5
#define SESSION_ID 10086001
#define SESSION_TIMEOUT 60typedef struct
{int rtpSendFd;int rtpPort;int bPlayFlag; // 播放標志char *cliIp;
} RTP_Send_t;typedef enum
{RTP_NULL,RTP_PLAY,RTP_PLAYING,RTP_STOP,
} RTP_PLAY_STATE;static int createUdpSocket()
{int fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd < 0)return -1;int on = 1;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));return fd;
}static int rtpSendH264Frame(int socket, char *ip, int16_t port,struct RtpPacket *rtpPacket, uint8_t *frame, uint32_t frameSize)
{uint8_t naluType; // nalu第一個字節int sendBytes = 0;int ret;naluType = frame[0];if (frameSize <= RTP_MAX_PKT_SIZE) // nalu長度小于最大包場:單一NALU單元模式{/**   0 1 2 3 4 5 6 7 8 9*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*  |F|NRI|  Type   | a single NAL unit ... |*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*/memcpy(rtpPacket->payload, frame, frameSize);ret = rtpSendPacket(socket, ip, port, rtpPacket, frameSize);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS就不需要加時間戳goto out;}else // nalu長度大于最大包場:分片模式{/**  0                   1                   2*  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | FU indicator  |   FU header   |   FU payload   ...  |* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*//**     FU Indicator*    0 1 2 3 4 5 6 7*   +-+-+-+-+-+-+-+-+*   |F|NRI|  Type   |*   +---------------+*//**      FU Header*    0 1 2 3 4 5 6 7*   +-+-+-+-+-+-+-+-+*   |S|E|R|  Type   |*   +---------------+*/int pktNum = frameSize / RTP_MAX_PKT_SIZE;        // 有幾個完整的包int remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小int i, pos = 1;/* 發送完整的包 */for (i = 0; i < pktNum; i++){rtpPacket->payload[0] = (naluType & 0x60) | 28;rtpPacket->payload[1] = naluType & 0x1F;if (i == 0)                                     // 第一包數據rtpPacket->payload[1] |= 0x80;              // startelse if (remainPktSize == 0 && i == pktNum - 1) // 最后一包數據rtpPacket->payload[1] |= 0x40;              // endmemcpy(rtpPacket->payload + 2, frame + pos, RTP_MAX_PKT_SIZE);ret = rtpSendPacket(socket, ip, port, rtpPacket, RTP_MAX_PKT_SIZE + 2);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;pos += RTP_MAX_PKT_SIZE;}/* 發送剩余的數據 */if (remainPktSize > 0){rtpPacket->payload[0] = (naluType & 0x60) | 28;rtpPacket->payload[1] = naluType & 0x1F;rtpPacket->payload[1] |= 0x40; // endmemcpy(rtpPacket->payload + 2, frame + pos, remainPktSize + 2);ret = rtpSendPacket(socket, ip, port, rtpPacket, remainPktSize + 2);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;}}out:return sendBytes;
}void *sendRtp(void *arg)
{RTP_Send_t *pRtpSend = (RTP_Send_t *)arg;int rtp_send_fd = pRtpSend->rtpSendFd;int rtpPort = pRtpSend->rtpPort;char *cli_ip = pRtpSend->cliIp;struct RtpPacket *rtpPacket = (struct RtpPacket *)malloc(sizeof(struct RtpPacket) + (1920 * 1080 * 4));rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,0, 0, 0x88923423);// h264H264ReaderInfo_t h264Info;if (H264_FileOpen(H264_FILE_NAME, &h264Info) < 0){printf("failed to open %s\n", H264_FILE_NAME);return NULL;}H264Frame_t h264Frame;h264Frame.pFrameBuf = (unsigned char *)malloc(1920 * 1080 * 4);while (pRtpSend->bPlayFlag){if (!H264_IsEndOfFile(&h264Info)){h264Frame.isLastFrame = 0;H264_GetFrame(&h264Frame, &h264Info);if (h264Frame.pFrameBuf != NULL){if (h264Frame.isLastFrame) // 最后一幀,移到開頭重新讀{printf("warning SeekFile 1\n");H264_SeekFile(&h264Info);}// printf("rtpSendH264Frame, frameNum=%d, time=%u\n", h264Info.frameNum, rtpPacket->rtpHeader.timestamp);rtpSendH264Frame(rtp_send_fd, cli_ip, rtpPort, rtpPacket,h264Frame.pFrameBuf + h264Frame.startcode_len,h264Frame.frame_len - h264Frame.startcode_len);rtpPacket->rtpHeader.timestamp += 90000 / FPS; // RTP 傳輸視頻每秒 90k HZusleep(1000 * 1000 / FPS);}}else{printf("warning need SeekFile 1\n");}}free(h264Frame.pFrameBuf);free(rtpPacket);H264_FileClose(&h264Info);
}// 解析RTSP請求
static void rtsp_request_parse(char *buffer, char *method, char *url, int *cseq, int *pRtpPort)
{char *line = strtok(buffer, "\r\n");sscanf(line, "%s %s RTSP/1.0", method, url);while ((line = strtok(NULL, "\r\n")) != NULL){if (strncmp(line, "CSeq:", 5) == 0){sscanf(line, "CSeq: %d", cseq);}char *pCliPort = strstr(line, "client_port=");if (pCliPort != NULL){int rtcpPort = 0;sscanf(pCliPort, "client_port=%d-%d", pRtpPort, &rtcpPort);// printf("rtpPort: %d-%d\n",*pRtpPort, rtcpPort);}}
}// 生成SDP描述
const char *generate_sdp()
{return "v=0\r\n""o=- 0 0 IN IP4 0.0.0.0\r\n""s=Example Stream\r\n""t=0 0\r\n""m=video 0 RTP/AVP 96\r\n""a=rtpmap:96 H264/90000\r\n""a=control:streamid=0\r\n";
}void rtsp_handle_OPTION(char *response, int cseq)
{sprintf(response,"RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN\r\n\r\n",cseq);
}static void rtsp_handle_DESCRIBE(char *response, int cseq)
{sprintf(response,"RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Content-Type: application/sdp\r\n""Content-Length: %zu\r\n\r\n%s",cseq, strlen(generate_sdp()), generate_sdp());
}static void rtsp_handle_SETUP(char *response, int cseq, int rtpPort)
{sprintf(response,"RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Session: %u; timeout=%d\r\n""Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n\r\n",cseq, SESSION_ID, SESSION_TIMEOUT, rtpPort, rtpPort + 1, RTP_PORT, RTP_PORT + 1);
}static void rtsp_handle_PLAY(char *response, int cseq)
{sprintf(response,"RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Session: %u; timeout=%d\r\n""Range: npt=0.000-\r\n\r\n",cseq, SESSION_ID, SESSION_TIMEOUT);
}static void rtsp_handle_TEARDOWN(char *response, int cseq)
{sprintf(response,"RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Session: %d; timeout=%d\r\n\r\n",cseq, SESSION_ID, SESSION_TIMEOUT);
}// 處理客戶端連接
int handle_client(int cli_fd, int rtp_send_fd, char *cli_ip)
{int client_sock = cli_fd;char buffer[1024] = {0};int cseq = 0;int rtpPort = 0;unsigned char bSendFlag = RTP_NULL;RTP_Send_t rtpSend;pthread_t thread_id;while (1){memset(buffer, 0, sizeof(buffer));int len = read(client_sock, buffer, sizeof(buffer) - 1);if (len <= 0)break;printf("C->S [%s]\n\n", buffer);char method[16] = {0};char url[128] = {0};rtsp_request_parse(buffer, method, url, &cseq, &rtpPort);char response[1024] = {0}; // 構造響應if (strcmp(method, "OPTIONS") == 0){rtsp_handle_OPTION(response, cseq);}else if (strcmp(method, "DESCRIBE") == 0){rtsp_handle_DESCRIBE(response, cseq);}else if (strcmp(method, "SETUP") == 0){rtsp_handle_SETUP(response, cseq, rtpPort);}else if (strcmp(method, "PLAY") == 0){rtsp_handle_PLAY(response, cseq);bSendFlag = RTP_PLAY;}else if (strcmp(method, "TEARDOWN") == 0){rtsp_handle_TEARDOWN(response, cseq);bSendFlag = RTP_STOP;}else{snprintf(response, sizeof(response),"RTSP/1.0 501 Not Implemented\r\nCSeq: %d\r\n\r\n", cseq);}write(client_sock, response, strlen(response));printf("S->C [%s]\n\n", response);if (bSendFlag == RTP_PLAY) // PLAY{rtpSend.rtpSendFd = rtp_send_fd;rtpSend.rtpPort = rtpPort;rtpSend.cliIp = cli_ip;rtpSend.bPlayFlag = 1;// 這里不使用線程的話,會一直無法處理 client_sock 發過來的 OPTION 消息,導致播放出問題if (pthread_create(&thread_id, NULL, (void *)sendRtp, (void *)&rtpSend) < 0){perror("pthread_create");}bSendFlag = RTP_PLAYING;}if (bSendFlag == RTP_STOP) // TEARDOWN{rtpSend.bPlayFlag = 0;pthread_join(thread_id); // 等待線程結束bSendFlag = RTP_NULL;break;}}printf("close ip=[%s] fd=[%d]\n", cli_ip, client_sock);close(client_sock);return 0;
}int main()
{int server_fd, client_fd;struct sockaddr_in address;int opt = 1;socklen_t addrlen = sizeof(address);// 創建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){perror("socket failed");return -1;}// 設置套接字選項if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))){perror("setsockopt");return -1;}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(RTSP_PORT);// 綁定端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0){perror("bind failed");return -1;}// 開始監聽if (listen(server_fd, MAX_CLIENTS) < 0){perror("listen");return -1;}// 用于發送 rtp 包的udp套接字int rtp_send_fd = createUdpSocket();if (rtp_send_fd < 0){printf("failed to create socket\n");return -1;}address.sin_port = htons(RTP_PORT);if (bind(rtp_send_fd, (struct sockaddr *)&address, sizeof(address)) < 0){perror("rtp_send_fd bind failed");return -1;}printf("RTSP Server listening on port %d\n", RTSP_PORT);// 主循環接受連接,目前處理一個客戶端while (1){char cli_ip[40] = {0};if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0){perror("accept");return -1;}strncpy(cli_ip, inet_ntoa(address.sin_addr), sizeof(cli_ip));printf("handle cliend [%s]\n", cli_ip);handle_client(client_fd, rtp_send_fd, cli_ip);}return 0;
}

將上面代碼保存在同一個目錄后,并且在同目錄里放一個.h264文件,然后運行 gcc *.c -lpthread 編譯,再執行./a.out運行程序,下面是我運行的過程:
在這里插入圖片描述


在這里插入圖片描述

🎄四、總結

本文介紹了實現介紹怎么通過RTSP協議通信,將H264視頻數據封裝成RTP包并發送的,也提供了實現源碼和運行結果,可以幫助讀者快速了解怎樣實現一個最簡單的RTSP服務器。

在這里插入圖片描述
如果文章有幫助的話,點贊👍、收藏?,支持一波,謝謝 😁😁😁

參考:
https://blog.csdn.net/huabiaochen/article/details/104557971

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87028.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87028.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87028.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

零開始git使用教程-傳html文件

1. 準備工作 (1) 確保你已經安裝&#xff1a; Visual Studio (VS)&#xff08;任何版本&#xff0c;社區版也行&#xff09; Git&#xff08;去官網 git-scm.com 下載安裝&#xff09; (2) 注冊 Gitee/GitHub 賬號 國內推薦 Gitee&#xff08;碼云&#xff09;&#xff1a;…

CPT204-Advanced OO Programming: Lists, Stacks, Queues, and Priority Queues

目錄 1.Java 集合框架層次結構Java Collection Framework hierarchy 1.1Java 集合框架描述&#xff1a; 1.2數據結構Data structures 1.3 Java 集合框架支持兩種類型的容器&#xff08;數據結構&#xff09;&#xff1a; 1.4 Java 集合框架的設計 2.Collection 2.1 Coll…

【網絡安全】Mysql注入中鎖機制

前言 在sql注入的延時注入中&#xff0c;常見的函數有sleep()直接延時、BENCHMARK()通過讓數據庫進行大量的計算而達到延時的效果、笛卡爾積、正則匹配等&#xff0c;但還有一個常常被忽略的函數&#xff0c;也就是Mysql中的鎖機制。雖然早些年就已經出現過相關的技術文章&…

博途多重背景、參數實例

1&#xff1a;我們在博途中先新建一個工程&#xff0c;并且建立一個FB塊名字為motor_fb&#xff0c;同樣建立一個FC塊名字為MOTOR_FC&#xff0c;里面寫上我們電機程序里常用的邏輯控制。二者程序內容相同。下面是motor_fb塊的程序截圖: 2:我們再新建一個FB塊&#xff0c;名字為…

運維的利器–監控–zabbix–第三步:配置zabbix–中間件–Tomcat–步驟+驗證

&#x1f3e0;個人主頁&#xff1a;fo安方的博客? &#x1f482;個人簡歷&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;目前中南大學MBA在讀&#xff0c;也考取過HCIE Cloud Computing、CCIE Security、PMP、CISP、RHCE、CCNP RS、PEST 3等證書。&#x1f433; &…

大模型在重癥哮喘手術全流程風險預測與治療方案制定中的應用研究

目錄 一、引言 1.1 研究背景與意義 1.2 研究目標與方法 1.3 研究創新點 二、重癥哮喘概述 2.1 定義與發病機制 2.2 分類與臨床表現 2.3 診斷標準與方法 三、大模型技術原理與應用現狀 3.1 大模型的基本原理 3.2 在醫療領域的應用案例分析 3.3 適用于重癥哮喘預測的…

Webpack的插件機制Tapable

Tapable 是一個輕量級的庫&#xff0c;用于創建和管理插件鉤子&#xff08;hooks&#xff09;&#xff0c;它在 Webpack 中廣泛應用&#xff0c;用于實現插件系統。Tapable 提供了一種機制&#xff0c;允許插件在特定的生命周期階段插入自定義邏輯&#xff0c;從而擴展應用程序…

FRONT歸因-兩階段訓練流程

FRONT, Fine-Grained Grounded Citations歸因 FRONT歸因&#xff0c;首先從檢索到的源文檔中選擇支持性引用&#xff0c;然后基于這些引用指導生成過程&#xff0c;確保生成回答有據可依&#xff0c;引用準確無誤。 FRONT的特色在于兩階段歸因訓練&#xff0c;要點如下: 階…

單端轉差分放大器AD8138

根據 AD8138 的數據手冊特性及參數&#xff0c;可以實現單端 5Vpp&#xff08;偏置 0V&#xff09;正弦波轉差分 5Vpp&#xff08;共模 2.5V&#xff09;的功能&#xff0c;但需注意以下細節&#xff1a; 1. 信號幅度匹配性 輸入信號&#xff1a;單端 5Vpp&#xff08;峰峰值…

用R包mice進行多重插補

利用R包mice實現的鏈式方程多重插補方法來插補缺失的數據。 所有多重插補方法都遵循三個步驟 插補——與單次插補類似&#xff0c;對缺失值進行插補。但是&#xff0c;插補值會從分布中提取m次&#xff0c;而不是僅提取一次。此步驟結束時&#xff0c;應該有m 個完整的數據集…

【專題】網絡攻防技術期末復習資料

網絡攻防技術期末復習資料 鏈接&#xff1a;https://blog.csdn.net/Pqf18064375973/article/details/148996272?sharetypeblogdetail&sharerId148996272&sharereferPC&sharesourcePqf18064375973&sharefrommp_from_link 網絡安全威脅的成因。 分類&#xff1a…

地震災害的模擬

為確保地震災害模擬的準確性和高效性&#xff0c;涉及的系統需要處理復雜的物理模型、數據輸入和多層次的模擬過程。在技術設計方案中&#xff0c;我們將涵蓋以下幾個方面&#xff1a; 背景&#xff1a;描述該模擬系統的目的與應用場景。需求&#xff1a;列出系統的功能需求&a…

9.9 《1/10成本實現GPT-3.5級表現!ChatGLM3-6B QLoRA微調實戰:4bit量化+低秩適配全解析》

1/10成本實現GPT-3.5級表現!ChatGLM3-6B QLoRA微調實戰:4bit量化+低秩適配全解析 ChatGLM3-6B 微調入門實戰:QLoRA 量化低秩適配技術 ▲ ChatGLM3-6B采用GLM架構改進版,支持32K上下文長度和代碼生成能力 一、QLoRA 技術原理精要 QLoRA(Quantized Low-Rank Adaptation)…

【Python基礎】11 Python深度學習生態系統全景解析:從基礎框架到專業應用的技術深度剖析(超長版,附多個代碼及結果)

引言:Python在深度學習領域的統治地位 在人工智能浪潮席卷全球的今天,Python已經成為深度學習領域當之無愧的王者語言。這不僅僅是因為Python語法簡潔易學,更重要的是圍繞Python構建的深度學習生態系統的完整性和強大性。從Google的TensorFlow到Facebook的PyTorch,從科學計…

RESTful API 設計原則深度解析

在 Web 服務架構中&#xff0c;RESTful API作為一種輕量級、可擴展的接口設計風格&#xff0c;通過 HTTP 協議實現資源的標準化訪問。本文從核心原則、URL 設計、HTTP 方法應用、狀態管理及面試高頻問題五個維度&#xff0c;結合工程實踐與反例分析&#xff0c;系統解析 RESTfu…

java web2(黑馬)

數據庫設計 簡介 1.軟件的研發步驟 2.數據庫設計概念 > 數據庫設計就是根據業務系統的具體需求&#xff0c;結合我們所選用的DBMS&#xff0c;為這個業務系統構造出最優 的數據存儲模型 > 建立數據庫中的表結構以及表與表之間的關聯關系的過程&#xff0c; > …

Meta 宣布加入 Kotlin 基金會,將為 Kotlin 和 Android 生態提供全新支持

近日 Meta 正式宣發加入了 Kotlin 基金會&#xff0c;如果你對 Meta 不熟悉&#xff0c;那么對于開源了 React Native 的 Facebook 應該不陌生了吧&#xff1f;現在它也正式加入了 Kotlin 領導者的陣營&#xff1a; Kotlin 基金會 是由 Jetbrains 和 Google 共同成立的基金會&a…

緩存系統-淘汰策略

目錄 一、LRU&#xff08;最近最少使用&#xff09; 工作原理 操作流程 基本特征 二、LFU&#xff08;最不常使用&#xff09; 工作原理 操作流程 基本特征 三、ARC 自適應 工作原理 操作流程 基本特征 四、TTL&#xff08;生存時間&#xff09; 工作原理 操作流…

TypeScript 安裝使用教程

一、TypeScript 簡介 TypeScript 是由微軟開發的開源編程語言&#xff0c;是 JavaScript 的超集&#xff0c;添加了靜態類型、接口、枚舉、類等特性&#xff0c;使開發大型應用更安全、可維護、可擴展。最終會被編譯為標準的 JavaScript 代碼在瀏覽器或 Node.js 中運行。 二、…

強化學習系列--dpo損失函數

DPO 概要 DPO&#xff08;Direct Preference Optimization&#xff0c;直接偏好優化&#xff09;是由斯坦福大學等研究團隊于2023年提出的一種偏好優化算法&#xff0c;可用于LLM、VLM與MLLM的對齊訓練。 算法基于PPO的RLHF基礎上進行了大幅簡化。DPO算法跳過了訓練獎勵模型這…