一、为什么需要上传进度回调
在嵌入式设备开发中,文件上传是个再常见不过的需求了。想象一下,你正在开发一个智能摄像头,需要把录制的视频上传到云端。如果用户盯着屏幕看了半天,连上传了多少都不知道,那体验得多糟糕啊!这就好比你去餐厅吃饭,服务员端了盘菜上来,却死活不告诉你还要等多久才能上齐。
OBS(对象存储服务)作为常用的云存储方案,提供了完善的上传接口。但默认情况下,它并不会主动告诉你上传进度。这就需要我们开发回调函数,让设备能够实时汇报"我现在传了多少"。
二、C++实现OBS上传进度回调
2.1 基本回调函数结构
我们先来看一个最简单的回调函数实现(技术栈:C++17 + OBS SDK):
/**
* 上传进度回调函数
* @param uploaded 已上传字节数
* @param total 文件总字节数
* @param userData 用户自定义数据指针
*/
void UploadProgressCallback(int64_t uploaded, int64_t total, void* userData) {
// 计算上传百分比
double percentage = (total > 0) ? (uploaded * 100.0 / total) : 0;
// 这里假设userData是设备显示屏对象指针
auto display = static_cast<DeviceDisplay*>(userData);
if (display) {
display->UpdateProgress(percentage); // 更新设备显示
}
// 同时输出到日志
std::cout << "上传进度: " << std::fixed << std::setprecision(2)
<< percentage << "%" << std::endl;
}
2.2 集成到OBS上传流程
光有回调函数还不够,我们需要把它挂载到OBS的上传流程中:
bool UploadFileToOBS(const std::string& filePath, DeviceDisplay* display) {
// 初始化OBS客户端
auto client = InitializeOBSClient();
// 设置上传参数
PutObjectRequest request;
request.SetBucketName("my-bucket");
request.SetKey("device_videos/" + GetFileName(filePath));
request.SetFilePath(filePath);
// 关键点:设置进度回调
request.setTransferProgressHandler(UploadProgressCallback, display);
// 执行上传
auto outcome = client.PutObject(request);
return outcome.IsSuccess();
}
三、进阶实现技巧
3.1 平滑进度显示
直接使用原始回调会导致更新过于频繁,我们可以添加平滑处理:
class SmoothProgress {
public:
void Update(int64_t uploaded, int64_t total) {
auto now = std::chrono::steady_clock::now();
if (now - lastUpdate_ < std::chrono::milliseconds(200)) {
return; // 200ms内不重复更新
}
lastUpdate_ = now;
// 计算EMA(指数移动平均)平滑处理
double current = (total > 0) ? (uploaded * 100.0 / total) : 0;
smoothValue_ = 0.3 * current + 0.7 * smoothValue_;
UpdateDisplay(smoothValue_);
}
private:
std::chrono::steady_clock::time_point lastUpdate_;
double smoothValue_ = 0;
};
3.2 断点续传支持
对于大文件上传,断点续传是必须的:
void ResumeUploadProgress(int64_t uploaded, int64_t total, void* userData) {
auto context = static_cast<ResumeContext*>(userData);
if (!context) return;
// 保存已上传字节数到本地
SaveResumePosition(context->resumeFile, uploaded);
// 更新显示
UpdateProgressDisplay(uploaded, total);
}
四、实际应用中的注意事项
- 线程安全:OBS SDK的回调可能发生在工作线程,UI更新需要线程间通信
- 资源释放:确保userData的生命周期覆盖整个上传过程
- 异常处理:网络波动时要能恢复进度显示
- 性能考量:嵌入式设备资源有限,避免在回调中做复杂计算
这里给出一个线程安全的实现示例:
class ThreadSafeProgress {
public:
void Update(int64_t uploaded, int64_t total) {
std::lock_guard<std::mutex> lock(mutex_);
currentUploaded_ = uploaded;
currentTotal_ = total;
// 通过消息队列通知UI线程
Message msg{MSG_UPDATE_PROGRESS, CalculatePercentage()};
messageQueue_.Push(msg);
}
private:
std::mutex mutex_;
int64_t currentUploaded_ = 0;
int64_t currentTotal_ = 0;
MessageQueue messageQueue_;
};
五、性能优化实践
对于资源受限的嵌入式设备,我们可以做这些优化:
- 降低回调频率:设置最小更新间隔
- 整数运算优先:避免浮点运算消耗
- 内存池管理:复用回调相关的内存对象
优化后的回调示例:
void OptimizedProgressCallback(int64_t uploaded, int64_t total, void* userData) {
static int64_t lastReported = 0;
const int64_t reportInterval = 1024 * 1024; // 每1MB报告一次
if (uploaded - lastReported < reportInterval && uploaded != total) {
return;
}
lastReported = uploaded;
// 使用整数运算计算百分比(0-10000表示0.00%-100.00%)
uint32_t permille = (total > 0) ? (uploaded * 10000 / total) : 0;
UpdateProgressPermille(permille);
}
六、完整示例代码
下面是一个完整的嵌入式设备上传示例:
#include <iostream>
#include <memory>
#include <mutex>
#include <obs-sdk-cpp.h>
class DeviceUploader {
public:
explicit DeviceUploader(std::shared_ptr<DeviceDisplay> display)
: display_(std::move(display)) {}
bool UploadVideo(const std::string& localPath) {
// 初始化OBS客户端
ObsClient client = CreateOBSClient();
// 设置上传请求
PutObjectRequest request;
request.SetBucket("device-videos");
request.SetKey(GenerateRemoteName(localPath));
request.SetFile(localPath);
// 设置进度回调(传递this指针作为userData)
request.SetProgressHandler(&DeviceUploader::ProgressCallback, this);
// 执行上传
auto outcome = client.PutObject(request);
if (!outcome.IsSuccess()) {
display_->ShowError("上传失败: " + outcome.GetError());
return false;
}
return true;
}
private:
static void ProgressCallback(int64_t uploaded, int64_t total, void* userData) {
auto self = static_cast<DeviceUploader*>(userData);
self->HandleProgress(uploaded, total);
}
void HandleProgress(int64_t uploaded, int64_t total) {
std::lock_guard<std::mutex> lock(mutex_);
// 计算百分比(0-100)
int percent = 0;
if (total > 0) {
percent = static_cast<int>(uploaded * 100 / total);
percent = std::min(100, std::max(0, percent)); // 钳制到0-100
}
// 更新显示
display_->UpdateProgress(percent);
// 每10%或完成时记录日志
if (percent % 10 == 0 || percent == 100) {
Log("上传进度: " + std::to_string(percent) + "%");
}
}
std::shared_ptr<DeviceDisplay> display_;
std::mutex mutex_;
};
七、应用场景与技术选型
这种技术特别适合以下场景:
- 智能家居设备的固件升级
- 安防设备的录像备份
- 工业设备的日志上传
- 医疗设备的报告传输
相比其他方案,C++实现的优势在于:
- 性能高效:直接内存操作,无运行时开销
- 资源可控:精确管理内存和CPU使用
- 跨平台:可移植到各种嵌入式系统
但也要注意:
- C++开发复杂度较高
- 需要手动管理内存
- 异常处理较为繁琐
八、总结与建议
实现一个健壮的上传进度回调系统,关键在于:
- 处理好线程安全问题
- 优化回调频率避免性能问题
- 提供平滑的进度显示体验
- 考虑嵌入式设备的资源限制
对于不同的应用场景,可以灵活调整:
- 消费级设备:侧重流畅的UI体验
- 工业设备:强调可靠性和实时性
- 医疗设备:需要最高级别的数据完整性
最后记住,好的进度反馈能让用户感受到产品的专业性和可靠性,这在嵌入式设备领域尤为重要。
评论