Qt/C++ 音视频开发 - FFmpeg保存裸流

Qt/C++ 音视频开发 - FFmpeg保存裸流

介绍

FFmpeg 是一个开源的多媒体框架,能够处理音频、视频及其他多媒体文件和流。裸流指的是未经封装处理的原始音视频数据。使用FFmpeg保存裸流,可以通过高效的方式处理并存储音视频数据。

应用使用场景

  • 实时流媒体传输:在没有时间进行复杂封装的情况下,将音视频数据直接传输。
  • 低延迟应用:减少因为封装产生的冗余数据和延迟。
  • 数据分析:对原始音视频数据进行深入分析时,不需要解封装过程。
  • 嵌入式设备:资源有限的设备上,避免额外的封装步骤以节省内存和CPU资源。

以下是一些示例代码,用于实现实时流媒体传输、低延迟应用、数据分析和嵌入式设备上的音视频数据直接传输。我们分别使用了Python和C语言示范这一过程。

实时流媒体传输与低延迟应用 (Python)

使用 socket 库进行简单的实时音视频传输。

服务器端:
import socket

def stream_server(host='0.0.0.0', port=12345):
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((host, port))
    server_socket.listen(1)
    connection, address = server_socket.accept()
    
    print(f"Connection from: {address}")
    
    while True:
        data = connection.recv(4096)
        if not data:
            break
        # Process the received raw audio/video data
        print(f"Received data: {data[:10]}...")  # Display part of the data for demonstration

    connection.close()

if __name__ == "__main__":
    stream_server()
客户端:
import socket

def stream_client(host='localhost', port=12345):
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((host, port))

    with open('sample_video.raw', 'rb') as file:
        chunk = file.read(4096)
        while chunk:
            client_socket.send(chunk)
            chunk = file.read(4096)

    client_socket.close()

if __name__ == "__main__":
    stream_client()

数据分析 - Python分析原始数据

import numpy as np

def analyze_raw_audio_data(file_path):
    with open(file_path, 'rb') as file:
        data = np.frombuffer(file.read(), dtype=np.int16)
    
    # Perform analysis like computing average amplitude
    avg_amplitude = np.mean(np.abs(data))
    print(f"Average Amplitude: {avg_amplitude}")

if __name__ == "__main__":
    analyze_raw_audio_data('sample_audio.raw')

嵌入式设备 - C语言直接传输数据

在嵌入式设备上,避免封装步骤以节省内存和CPU资源。

服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 12345
#define BUFFER_SIZE 4096

void stream_server() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, 
                       (socklen_t*)&addrlen))<0) {
        perror("accept");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    ssize_t valread;
    while ((valread = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
        // Process the received raw audio/video data
        printf("Received %zd bytes\n", valread);
    }

    close(new_socket);
    close(server_fd);
}

int main() {
    stream_server();
    return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 12345
#define BUFFER_SIZE 4096

void stream_client(const char *filename) {
    struct sockaddr_in address;
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    FILE *file;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return;
    }

    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
        printf("\nInvalid address/ Address not supported \n");
        return;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return;
    }

    file = fopen(filename, "rb");
    if (!file) {
        perror("File opening failed");
        close(sock);
        return;
    }

    size_t nread;
    while ((nread = fread(buffer, 1, BUFFER_SIZE, file)) > 0) {
        send(sock, buffer, nread, 0);
    }

    fclose(file);
    close(sock);
}

int main() {
    stream_client("sample_video.raw");
    return 0;
}

原理解释

保存裸流的核心在于直接处理音视频编码后的数据,不进行封装。一般流程如下:

  1. 初始化编码器和解码器。
  2. 打开输入源(如摄像头、麦克风等)。
  3. 读取输入帧,进行编码。
  4. 将编码后的数据直接写入输出文件或网络流中。

算法原理流程图

初始化编码器和解码器
打开输入源
读取输入帧
编码数据
写入输出

算法原理解释

  1. 初始化编码器和解码器:设置编码参数,如码率、分辨率、采样率等。
  2. 打开输入源:获取输入设备句柄或者文件路径。
  3. 读取输入帧:从输入源读取一帧原始数据。
  4. 编码数据:使用FFmpeg的编码器将原始数据转换为压缩格式。
  5. 写入输出:将编码后的数据直接写入文件或网络流。

实际应用代码示例实现

示例代码

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

int main(int argc, char *argv[]) {
    av_register_all();
    AVFormatContext *pFormatCtx = nullptr;

    avformat_open_input(&pFormatCtx, argv[1], nullptr, nullptr);
    avformat_find_stream_info(pFormatCtx, nullptr);

    int videoStreamIndex = -1;
    for (unsigned i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break;
        }
    }

    AVCodecContext *pCodecCtx = avcodec_alloc_context3(nullptr);
    avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStreamIndex]->codecpar);
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    avcodec_open2(pCodecCtx, pCodec, nullptr);

    AVPacket packet;
    FILE *outFile = fopen("output.raw", "wb");

    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        if (packet.stream_index == videoStreamIndex) {
            fwrite(packet.data, 1, packet.size, outFile);
        }
        av_packet_unref(&packet);
    }

    fclose(outFile);
    avcodec_free_context(&pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}

测试代码

编译和运行上述代码:

g++ -o save_raw_stream save_raw_stream.cpp -lavformat -lavcodec -lavutil -lswscale
./save_raw_stream input.mp4

部署场景

该方案适用于各类需要处理原始音视频数据的应用,包括但不限于:

  • 嵌入式设备视频监控
  • 视频会议系统
  • 实时流媒体服务器

材料链接

总结

使用FFmpeg保存裸流是一种高效的处理原始音视频数据的方法,适用于各种对延迟敏感的应用场景。尽管裸流缺乏封装信息,但其高效和简单的特点使其在特定领域内得到了广泛应用。

未来展望

随着多媒体技术的发展,更多的硬件加速技术会被引入,进一步提高裸流处理的效率。同时,未来可能会有新的标准和协议出现,以更好地支持裸流在不同场景中的应用。

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以使用FFmpeg库来保存视频文件,具体实现可以参考以下代码: ```c++ #include <iostream> #include <string> #include <sstream> #include <fstream> #include <chrono> #include <thread> #include <opencv2/opencv.hpp> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> using namespace std; using namespace cv; int main(int argc, char* argv[]) { // 初始化FFmpeg库 av_register_all(); avcodec_register_all(); // 打开视频文件 string filename = "test.mp4"; AVFormatContext* format_ctx = nullptr; if (avformat_open_input(&format_ctx, filename.c_str(), nullptr, nullptr) != 0) { cerr << "Failed to open video file " << filename << endl; return -1; } // 查找视频流 int video_stream_index = -1; for (unsigned int i = 0; i < format_ctx->nb_streams; i++) { if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { cerr << "Failed to find video stream in " << filename << endl; avformat_close_input(&format_ctx); return -1; } // 获取视频流的解码器 AVCodec* codec = avcodec_find_decoder(format_ctx->streams[video_stream_index]->codecpar->codec_id); if (codec == nullptr) { cerr << "Failed to find codec for video stream in " << filename << endl; avformat_close_input(&format_ctx); return -1; } // 打开解码器 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar) != 0) { cerr << "Failed to copy codec parameters to decoder context in " << filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } if (avcodec_open2(codec_ctx, codec, nullptr) != 0) { cerr << "Failed to open codec for video stream in " << filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } // 创建视频文件 string output_filename = "output.mp4"; AVFormatContext* output_format_ctx = nullptr; if (avformat_alloc_output_context2(&output_format_ctx, nullptr, nullptr, output_filename.c_str()) != 0) { cerr << "Failed to create output format context for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); return -1; } // 添加视频流 AVStream* output_stream = avformat_new_stream(output_format_ctx, nullptr); if (output_stream == nullptr) { cerr << "Failed to create output video stream for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } output_stream->id = output_format_ctx->nb_streams - 1; if (avcodec_parameters_copy(output_stream->codecpar, format_ctx->streams[video_stream_index]->codecpar) != 0) { cerr << "Failed to copy codec parameters to output video stream in " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } output_stream->codecpar->codec_tag = 0; if (avformat_write_header(output_format_ctx, nullptr) != 0) { cerr << "Failed to write output file header for " << output_filename << endl; avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); return -1; } // 分配解码缓存 AVFrame* frame = av_frame_alloc(); AVPacket packet; av_init_packet(&packet); // 读取视频帧并保存 while (av_read_frame(format_ctx, &packet) == 0) { if (packet.stream_index == video_stream_index) { if (avcodec_send_packet(codec_ctx, &packet) != 0) { cerr << "Failed to send packet to decoder in " << filename << endl; break; } while (avcodec_receive_frame(codec_ctx, frame) == 0) { // 将YUV图像转换为BGR图像 Mat bgr_image(frame->height, frame->width, CV_8UC3); SwsContext* sws_ctx = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, frame->width, frame->height, AV_PIX_FMT_BGR24, SWS_BILINEAR, nullptr, nullptr, nullptr); uint8_t* data[AV_NUM_DATA_POINTERS] = { 0 }; data[0] = bgr_image.data; int linesize[AV_NUM_DATA_POINTERS] = { 0 }; linesize[0] = bgr_image.step; sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, data, linesize); sws_freeContext(sws_ctx); // 保存BGR图像 AVPacket output_packet; av_init_packet(&output_packet); output_packet.data = nullptr; output_packet.size = 0; avcodec_send_frame(codec_ctx, frame); while (avcodec_receive_packet(codec_ctx, &output_packet) == 0) { output_packet.stream_index = output_stream->id; av_write_frame(output_format_ctx, &output_packet); av_packet_unref(&output_packet); } // 显示BGR图像 imshow("BGR Image", bgr_image); waitKey(1); } } av_packet_unref(&packet); } // 释放资源 av_write_trailer(output_format_ctx); avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); avformat_free_context(output_format_ctx); av_frame_free(&frame); return 0; } ``` 这段代码可以读取一个视频文件,将其中的每一帧转换为BGR格式的图像,并保存为另一个视频文件。其中,使用了OpenCV库来显示BGR图像,使用了FFmpeg库来读取和保存视频文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼弦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值