diff --git a/.github/workflows/call-update-changelog.yml b/.github/workflows/call-update-changelog.yml new file mode 100644 index 000000000..b76b9b3ff --- /dev/null +++ b/.github/workflows/call-update-changelog.yml @@ -0,0 +1,53 @@ +name: Call update changelog + +on: + workflow_dispatch: + inputs: + version: + description: "Target version; leave empty to auto bump" + required: false + type: string + name: + description: "Maintainer name used in debian/changelog" + required: true + type: string + email: + description: "Maintainer email used in debian/changelog" + required: true + type: string + base_branch: + description: "Base branch to read and target" + required: false + default: master + type: string + distribution: + description: "Changelog distribution" + required: false + default: unstable + type: string + create_pr: + description: "Create a pull request after generating changelog" + required: false + default: true + type: boolean + +jobs: + update_changelog: + uses: linuxdeepin/.github/.github/workflows/update-changelog.yml@master + with: + version: ${{ inputs.version }} + name: ${{ inputs.name }} + email: ${{ inputs.email }} + base_branch: ${{ inputs.base_branch }} + distribution: ${{ inputs.distribution }} + create_pr: ${{ inputs.create_pr }} + secrets: inherit + + show_result: + runs-on: ubuntu-latest + needs: update_changelog + steps: + - name: Show update changelog result + run: | + echo "Version: ${{ needs.update_changelog.outputs.version }}" + echo "Pull Request URL: ${{ needs.update_changelog.outputs.pr_url }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e0118edda --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +build/ +*.qm +*.prl +*.qmlls.ini + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +*_qmlcache.qrc \ No newline at end of file diff --git a/debian/changelog b/debian/changelog index 744e63413..c73dbabb1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +deepin-camera (6.0.13) unstable; urgency=medium + + * tag 6.0.13 + +-- zhangjiarui Thu, 30 Apr 2026 11:36:13 +0800 + +deepin-camera (6.0.12) unstable; urgency=medium + + * tag 6.0.12 + + -- shuaijie Mon, 19 Aug 2024 15:32:02 +0800 + deepin-camera (6.0.11) unstable; urgency=medium * tag 6.0.11 diff --git a/libcam/libcam/cameraconfig.c b/libcam/libcam/cameraconfig.c index 2fdf4a8a6..6cbb85657 100644 --- a/libcam/libcam/cameraconfig.c +++ b/libcam/libcam/cameraconfig.c @@ -48,27 +48,28 @@ static config_t my_config = .device_name = NULL, .device_location = NULL, .format = V4L2_PIX_FMT_MJPEG, - .render = "sdl", - .gui = "qt5", - .audio = "port", - .capture = "mmap", + .render = "sdl", + .gui = "qt5", + .audio = "port", + .capture = "mmap", .video_codec = "mjpg",/*yuy2,mjpg,mpeg,flv1,wmv1,mpg2,mp43,dx50,h264,hevc,vp80,vp90,theo*/ .audio_codec = "aac", - .profile_name = NULL, - .profile_path = NULL, + .profile_name = NULL, + .profile_path = NULL, .video_name = NULL, - .video_path = NULL, - .photo_name = NULL, - .photo_path = NULL, - .video_sufix = 1, - .photo_sufix = 1, - .fps_num = 1, + .video_path = NULL, + .photo_name = NULL, + .photo_path = NULL, + .video_sufix = 1, + .photo_sufix = 1, + .fps_num = 1, .fps_denom = 30, .audio_device = -1,/*will use API default in this case*/ - .video_fx = 0, /*no video fx*/ - .audio_fx = 0, /*no audio fx*/ - .osd_mask = 0, /*REND_OSD_NONE*/ - .crosshair_color=0x0000FF00, /*osd crosshair rgb color (0x00RRGGBB)*/ + .video_fx = 0, /*no video fx*/ + .audio_fx = 0, /*no audio fx*/ + .osd_mask = 0, /*REND_OSD_NONE*/ + .crosshair_color=0x0000FF00, /*osd crosshair rgb color (0x00RRGGBB)*/ + .verbosity=0 }; /* @@ -149,9 +150,10 @@ int config_save(const char *filename) fprintf(fp, "#OSD mask \n"); fprintf(fp, "osd_mask=0x%x\n", my_config.osd_mask); fprintf(fp, "crosshair_color=0x%x\n", my_config.crosshair_color); - + fprintf(fp, "#verbosity \n"); + fprintf(fp, "verbosity=%i\n", my_config.verbosity); /* return to system locale */ - setlocale(LC_NUMERIC, ""); + setlocale(LC_NUMERIC, ""); /* flush stream buffers to filesystem */ fflush(fp); @@ -231,7 +233,7 @@ int config_load(const char *filename) /*skip invalid lines */ if(!token || !value || strlen(token) < 1 || strlen(value) < 1) { - fprintf(stderr, "deepin-camera: (config) skiping invalid config entry at line %i\n", line); + fprintf(stderr, "deepin-camera: (config) skipping invalid config entry at line %i: %s\n", line, bufp); if(token) free(token); if(value) @@ -310,6 +312,10 @@ int config_load(const char *filename) free(my_config.photo_path); my_config.photo_path = strdup(value); } + else if(strcmp(token, "verbosity") == 0) + { + my_config.verbosity = (int) strtoul(value, NULL, 10); + } // else if(strcmp(token, "video_sufix") == 0) // { // my_config.video_sufix = (int) strtoul(value, NULL, 10); @@ -454,7 +460,9 @@ void config_update(options_t *my_options) free(my_config.photo_path); my_config.photo_path = strdup(my_options->photo_path); } - + /*check for verbosity options*/ + if(my_options->verbosity >= 0) + my_config.verbosity = my_options->verbosity; } /* diff --git a/libcam/libcam/cameraconfig.h b/libcam/libcam/cameraconfig.h index ed4f9b5e8..ac6e2d4cc 100644 --- a/libcam/libcam/cameraconfig.h +++ b/libcam/libcam/cameraconfig.h @@ -64,6 +64,7 @@ typedef struct _config_t { uint32_t audio_fx; uint32_t osd_mask; /*OSD bit mask*/ uint32_t crosshair_color; /*osd crosshair rgb color (0x00RRGGBB)*/ + int verbosity; } config_t; /*video sufix flag*/ diff --git a/libcam/libcam/camview.c b/libcam/libcam/camview.c index 45687bdd3..f52bda779 100644 --- a/libcam/libcam/camview.c +++ b/libcam/libcam/camview.c @@ -30,12 +30,14 @@ #include #include #include +#include #include /* support for internationalization - i18n */ #include #include #include "gviewv4l2core.h" +#include "core_time.h" #include "gviewrender.h" #include "gviewencoder.h" #include "gview.h" @@ -104,14 +106,53 @@ static int my_encoder_status = 0; static int is_wayland = 0; //是否是wayland的窗口管理器 +static int statusForceGles = 0; //forceGles 1,强走GLES + static int is_pgux = 0; //是否是pugx +static char libva_driver_name[200]; + +static char libvdpau_driver_name[200]; + static uint8_t soundTakePhoto = 1;//拍照声音提示 static char status_message[80]; static int encode_thread_running = 0; +static char project_id[200]; + +static char **card_keywords = NULL; +static int card_keyword_count = 0; + +static int is_invalid_card_keyword(const char *keyword) +{ + if (keyword == NULL || keyword[0] == '\0') { + return 1; + } + + for (const char *p = keyword; *p != '\0'; p++) { + if (!isspace((unsigned char)*p)) { + return 0; + } + } + + return 1; +} + +static void clear_card_keywords(void) +{ + if (card_keywords == NULL) { + return; + } + + for (int i = 0; i < card_keyword_count; i++) { + free(card_keywords[i]); + } + free(card_keywords); + card_keywords = NULL; + card_keyword_count = 0; +} void set_video_time_capture(double video_time) { @@ -688,6 +729,11 @@ static void *audio_processing_loop(void *data) render_set_osd_mask(osd_mask); + /* init reference to system monotonic clock on first run */ + if (audio_timestamp_reference == 0) { + audio_timestamp_reference = ns_time_monotonic(); + audio_timestamp_tmp = audio_timestamp_reference; + } while(video_capture_get_save_video()) { if(get_capture_pause()) @@ -695,7 +741,7 @@ static void *audio_processing_loop(void *data) int ret = audio_get_next_buffer(audio_ctx, audio_buff, sample_type, my_audio_mask); if(ret == 0) { - audio_pause_timestamp = audio_buff->timestamp - audio_timestamp_tmp; + audio_pause_timestamp = ns_time_monotonic() - audio_timestamp_tmp; } continue; } @@ -703,7 +749,7 @@ static void *audio_processing_loop(void *data) int ret = audio_get_next_buffer(audio_ctx, audio_buff, sample_type, my_audio_mask); - audio_timestamp_tmp = audio_buff->timestamp; + audio_timestamp_tmp = ns_time_monotonic(); if(audio_pause_timestamp != 0) { audio_timestamp_reference += audio_pause_timestamp; audio_pause_timestamp = 0; @@ -718,7 +764,7 @@ static void *audio_processing_loop(void *data) .tv_nsec = 1000000};/*nanosec*/ nanosleep(&req, NULL); } else if(ret == 0) { - encoder_ctx->enc_audio_ctx->pts = audio_buff->timestamp - audio_timestamp_reference; + encoder_ctx->enc_audio_ctx->pts = ns_time_monotonic() - audio_timestamp_reference; /*OSD vu meter level*/ render_set_vu_level(audio_buff->level_meter); @@ -1344,9 +1390,21 @@ void set_wayland_status(int status) is_wayland = status; } +void set_forceGles(int status) +{ + statusForceGles = status; +} + +int is_forceGles() +{ + return statusForceGles; +} + int get_wayland_status() { - return is_wayland; + if (1 == is_forceGles()) + return 0; + return is_wayland; } void set_takeing_photo_sound(uint8_t status) @@ -1368,3 +1426,110 @@ int get_pugx_status() { return is_pgux; } + +void set_project_id(const char *id) +{ + if (id != NULL) { + memset(project_id, 0, sizeof(project_id)); + strncpy(project_id, id, sizeof(project_id) - 1); + } else { + memset(project_id, 0, sizeof(project_id)); + } +} + +const char* get_project_id(void) +{ + return project_id; +} + +void set_card_keywords(const char **keywords, int count) +{ + clear_card_keywords(); + + if (keywords == NULL || count <= 0) { + return; + } + + int valid_count = 0; + for (int i = 0; i < count; i++) { + if (!is_invalid_card_keyword(keywords[i])) { + valid_count++; + } + } + + if (valid_count == 0) { + return; + } + + card_keywords = calloc((size_t)valid_count, sizeof(char *)); + if (card_keywords == NULL) { + fprintf(stderr, "deepin-camera: failed to allocate card keywords\n"); + return; + } + + for (int i = 0; i < count; i++) { + if (is_invalid_card_keyword(keywords[i])) { + continue; + } + + card_keywords[card_keyword_count] = strdup(keywords[i]); + if (card_keywords[card_keyword_count] != NULL) { + card_keyword_count++; + } + } +} + +const char** get_card_keywords(int *count) +{ + if (count != NULL) { + *count = card_keyword_count; + } + + return (const char **)card_keywords; +} + +void set_libva_driver_name(const char *name) +{ + if (name != NULL) { + memset(libva_driver_name, 0, sizeof(libva_driver_name)); + strncpy(libva_driver_name, name, sizeof(libva_driver_name) - 1); + } else { + memset(libva_driver_name, 0, sizeof(libva_driver_name)); + } +} + +const char* get_libva_driver_name(void) +{ + return libva_driver_name; +} + +void set_libvdpau_driver_name(const char *name) +{ + if (name != NULL) { + memset(libvdpau_driver_name, 0, sizeof(libvdpau_driver_name)); + strncpy(libvdpau_driver_name, name, sizeof(libvdpau_driver_name) - 1); + } else { + memset(libvdpau_driver_name, 0, sizeof(libvdpau_driver_name)); + } +} + +const char* get_libvdpau_driver_name(void) +{ + return libvdpau_driver_name; +} + +int is_driver_available(const char *driver_name) +{ + /* Check if driver name is not NULL, not empty, and not "none" or similar invalid values */ + if (driver_name != NULL && + driver_name[0] != '\0' && + strcmp(driver_name, "none") != 0 && + strcmp(driver_name, "null") != 0 && + strcmp(driver_name, "invalid") != 0 && + strcmp(driver_name, "undefined") != 0 && + strcmp(driver_name, "unknown") != 0) { + return 1; /* Valid driver exists */ + } + + return 0; /* No valid driver */ +} diff --git a/libcam/libcam/camview.h b/libcam/libcam/camview.h index 5efd5b6ab..625f0b83d 100644 --- a/libcam/libcam/camview.h +++ b/libcam/libcam/camview.h @@ -34,6 +34,7 @@ extern "C" { #define VERTEXIN 0 #define TEXTUREIN 1 +#define UOS2025073011543 "UOS2025073011543" typedef struct _capture_loop_data_t { void *options; void *config; @@ -435,6 +436,120 @@ void set_pugx_status(int status); int get_pugx_status(); +void set_forceGles(int status); + +int is_forceGles(); + +/* + * set project id + * args: + * project_id - project id string + * + * asserts: + * none + * + * returns: none + */ +void set_project_id(const char *project_id); + +/* + * get project id + * args: + * none + * + * asserts: + * none + * + * returns: pointer to project id string + */ +const char* get_project_id(void); + +/* + * set camera card keywords + * args: + * keywords - camera card keyword string array. Each string is copied by this function; + * caller retains ownership of the input array and strings. + * count - keyword count + * + * asserts: + * none + * + * returns: none + */ +void set_card_keywords(const char **keywords, int count); + +/* + * get camera card keywords + * args: + * count - output keyword count + * + * asserts: + * none + * + * returns: pointer to camera card keyword string array + */ +const char** get_card_keywords(int *count); + +/* + * set libva driver name + * args: + * name - libva driver name string + * + * asserts: + * none + * + * returns: none + */ +void set_libva_driver_name(const char *name); + +/* + * get libva driver name + * args: + * none + * + * asserts: + * none + * + * returns: pointer to libva driver name string + */ +const char* get_libva_driver_name(void); + +/* + * set libvdpau driver name + * args: + * name - libvdpau driver name string + * + * asserts: + * none + * + * returns: none + */ +void set_libvdpau_driver_name(const char *name); + +/* + * get libvdpau driver name + * args: + * none + * + * asserts: + * none + * + * returns: pointer to libvdpau driver name string + */ +const char* get_libvdpau_driver_name(void); + +/* + * check if driver is available and valid + * args: + * driver_name - pointer to driver name string + * + * asserts: + * none + * + * returns: 1 if valid driver exists, 0 otherwise + */ +int is_driver_available(const char *driver_name); + #ifdef __cplusplus } #endif diff --git a/libcam/libcam_audio/audio.c b/libcam/libcam_audio/audio.c index 3195bfb67..930fb8792 100644 --- a/libcam/libcam_audio/audio.c +++ b/libcam/libcam_audio/audio.c @@ -131,7 +131,13 @@ static void audio_free_buffers() for(i = 0; i < AUDBUFF_NUM; ++i) { + // 在兆芯处理器上,audio_buffers[i].data 概率性出现空指针/重复释放的情况,所以这里需要增加判断 + if (audio_buffers[i].data == NULL) { + continue; + } + free(audio_buffers[i].data); + audio_buffers[i].data = NULL; } free(audio_buffers); diff --git a/libcam/libcam_encoder/encoder.c b/libcam/libcam_encoder/encoder.c index e6ff475e9..32a8313b4 100644 --- a/libcam/libcam_encoder/encoder.c +++ b/libcam/libcam_encoder/encoder.c @@ -45,7 +45,10 @@ #include #include +#include + #include "cameraconfig.h" +#include "core_time.h" #include "gviewencoder.h" #include "encoder.h" #include "stream_io.h" @@ -579,12 +582,10 @@ static encoder_video_context_t *encoder_video_init(encoder_context_t *encoder_ct else video_codec_data->codec_context->time_base = (AVRational) {1, 15}; //fallback to 15 fps (e.g gspca) - if (video_defaults->gop_size > 0) { - if (pgux == 1) { - video_codec_data->codec_context->gop_size = 3; - } else { - video_codec_data->codec_context->gop_size = video_defaults->gop_size; + video_codec_data->codec_context->gop_size = video_defaults->gop_size; + if (pgux == 1 || is_forceGles()) { + video_codec_data->codec_context->max_b_frames = 0; } } else { video_codec_data->codec_context->gop_size = video_codec_data->codec_context->time_base.den; @@ -592,6 +593,8 @@ static encoder_video_context_t *encoder_video_init(encoder_context_t *encoder_ct switch (video_defaults->codec_id) { case AV_CODEC_ID_H264: { video_codec_data->codec_context->me_range = 16; + video_codec_data->codec_context->gop_size = 250; + video_codec_data->codec_context->max_b_frames = 0; //av_dict_set(&video_codec_data->private_options, "rc_lookahead", "1", 0); getAvutil()->m_av_dict_set(&video_codec_data->private_options, "crf", "23", 0); getAvutil()->m_av_dict_set(&video_codec_data->private_options, "preset", "ultrafast", 0); @@ -1359,7 +1362,7 @@ int encoder_flush_video_buffer(encoder_context_t *encoder_ctx) if (verbosity > 1) printf("ENCODER: flushed %i delayed video frames\n", flushed_frame_counter); - if (!buffer_count) { + if (!buffer_count) { fprintf(stderr, "ENCODER: (flush video buffer) max processed buffers reached\n"); return -1; } @@ -1579,6 +1582,61 @@ static int libav_get_encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packe // return ret; // } +/* + * Ensure output buffer is large enough for the encoded packet. + * Dynamically resizes the buffer if needed (1.5x reserved space, capped at + * max(width*height/2, 2MB)). Drops the frame if it exceeds the reasonable + * upper limit or if realloc fails. + * + * Returns: 1 if data was successfully copied to outbuf, + * 0 if frame was dropped (caller must continue to next iteration). + */ +static int resize_outbuf_if_needed(encoder_video_context_t *enc_video_ctx, + encoder_context_t *encoder_ctx, + AVPacket *pkt) +{ + /* Guard against invalid negative pkt->size to prevent memcpy overflow, + * since negative int is implicitly converted to a huge size_t value. */ + if (pkt->size < 0) { + fprintf(stderr, "ENCODER: invalid packet size %d, dropping frame\n", pkt->size); + return 0; + } + + if (pkt->size <= enc_video_ctx->outbuf_size) { + memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size); + return 1; + } + + /* Buffer limit: width * height / 2, with 2MB minimum protection */ + int max_reasonable_size = encoder_ctx->video_width * encoder_ctx->video_height / 2; + if (max_reasonable_size < 2 * 1024 * 1024) + max_reasonable_size = 2 * 1024 * 1024; + + if (pkt->size > max_reasonable_size) { + fprintf(stderr, "ENCODER: packet size %d exceeds reasonable limit (%d), dropping frame\n", + pkt->size, max_reasonable_size); + return 0; + } + + /* Reserve extra space (1.5x) to avoid frequent reallocations */ + int alloc_size = pkt->size + (pkt->size >> 1); + if (alloc_size > max_reasonable_size) + alloc_size = max_reasonable_size; + + fprintf(stderr, "ENCODER: resizing output buffer (%i -> %i, reserved %i)\n", + enc_video_ctx->outbuf_size, pkt->size, alloc_size); + uint8_t *new_buf = realloc(enc_video_ctx->outbuf, alloc_size); + if (new_buf) { + enc_video_ctx->outbuf = new_buf; + enc_video_ctx->outbuf_size = alloc_size; + memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size); + return 1; + } + + fprintf(stderr, "ENCODER: failed to allocate %d bytes, dropping frame\n", alloc_size); + return 0; +} + /* * encode video frame * args: @@ -1700,10 +1758,11 @@ int encoder_encode_video(encoder_context_t *encoder_ctx, void *input_frame) enc_video_ctx->flags = pkt->flags; enc_video_ctx->duration = pkt->duration; - if (pkt->size <= enc_video_ctx->outbuf_size) - memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size); - else - fprintf(stderr, "video packet size is bigger than output buffer(%i>%i)\n", pkt->size, enc_video_ctx->outbuf_size); + if (!resize_outbuf_if_needed(enc_video_ctx, encoder_ctx, pkt)) { + last_video_pts = enc_video_ctx->pts; + getLoadLibsInstance()->m_av_packet_unref(pkt); + continue; + } /* free any side data since we cannot return it */ if (pkt->side_data_elems > 0) { @@ -1883,6 +1942,7 @@ int encoder_encode_audio(encoder_context_t *encoder_ctx, void *audio_data) audio_codec_data->codec_context->time_base.den); } } + enc_audio_ctx->original_pts = enc_audio_ctx->pts; ret = libav_send_encode( audio_codec_data->codec_context, audio_codec_data->frame); @@ -1946,7 +2006,7 @@ int encoder_encode_audio(encoder_context_t *encoder_ctx, void *audio_data) void encoder_close(encoder_context_t *encoder_ctx) { encoder_clean_video_ring_buffer(); - + if (!encoder_ctx) return; @@ -2154,6 +2214,84 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int return err; } +// 检查是否支持VAAPI硬编码的函数 +int check_vaapi_support() { + struct pci_device_iterator *iter; + struct pci_device *dev; + VADisplay va_display; + VAStatus status; + int major_version, minor_version; + int is_vaapi_supported = 0; + + // 初始化PCI系统 + if (pci_system_init() != 0) { + fprintf(stderr, "无法初始化PCI系统\n"); + return 0; + } + + iter = pci_slot_match_iterator_create(NULL); + if (iter == NULL) { + fprintf(stderr, "无法创建PCI插槽匹配迭代器\n"); + pci_system_cleanup(); + return 0; + } + + va_display = vaGetDisplay(NULL); // 这里假设使用X11,如果是Wayland需要使用vaGetDisplayWl + if (va_display == NULL) { + fprintf(stderr, "无法打开VA显示\n"); + pci_iterator_destroy(iter); + pci_system_cleanup(); + return 0; + } + + status = vaInitialize(va_display, &major_version, &minor_version); + if (status != VA_STATUS_SUCCESS) { + fprintf(stderr, "无法初始化VAAPI\n"); + vaTerminate(va_display); + pci_iterator_destroy(iter); + pci_system_cleanup(); + return 0; + } + + // 遍历PCI设备 + while ((dev = pci_device_next(iter)) != NULL) { + if (0x03 == ((dev->device_class >> 16) & 0x0ff)) { // 检查是否为显示设备 + printf("检测到显示设备: %04x:%04x\n", dev->vendor_id, dev->device_id); + + // 检查支持的配置文件 + int num_profiles; + VAProfile *profiles; + status = vaQueryConfigProfiles(va_display, NULL, &num_profiles); + if (status != VA_STATUS_SUCCESS) { + fprintf(stderr, "无法查询配置文件\n"); + continue; + } + + profiles = (VAProfile *)malloc(sizeof(VAProfile) * num_profiles); + status = vaQueryConfigProfiles(va_display, profiles, &num_profiles); + if (status != VA_STATUS_SUCCESS) { + fprintf(stderr, "无法查询配置文件\n"); + free(profiles); + continue; + } + + printf("支持的VAAPI配置文件:\n"); + for (int i = 0; i < num_profiles; i++) { + printf(" Profile %d\n", profiles[i]); + is_vaapi_supported = 1; // 如果找到任何支持的配置文件,设置支持标志 + } + + free(profiles); + break; // 找到一个支持的设备就可以退出循环 + } + } + + vaTerminate(va_display); + pci_iterator_destroy(iter); + pci_system_cleanup(); + + return is_vaapi_supported; +} /* @@ -2170,50 +2308,15 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int static encoder_video_context_t *encoder_video_init_vaapi(encoder_context_t *encoder_ctx) { struct pci_device_iterator *iter; - struct pci_device *dev; int ret; is_vaapi = HW_VAAPI_OK; - ret = pci_system_init(); - if (ret != 0) { - fprintf(stderr, "Couldn't initialize PCI system\n"); + + if(!check_vaapi_support()) + { is_vaapi = HW_VAAPI_FAIL1; + fprintf(stderr, "no gpu card for vaapi\n"); return NULL; - } - iter = pci_slot_match_iterator_create(NULL); - - while (1) { - if ((dev = pci_device_next(iter)) == NULL) { - is_vaapi = HW_VAAPI_FAIL1; - fprintf(stderr, "no gpu card for vaapi\n"); - return NULL; - } - - if (0x03 == ((dev->device_class >> 16) & 0x0ff)) { - fprintf(stderr, " CLASS 0x%02x subclass 0x%02x prog-if_name 0x%02x REVISION 0x%02x\n", - (dev->device_class >> 16) & 0x0ff, - (dev->device_class >> 8) & 0x0ff, - (dev->device_class >> 0) & 0x0ff, - dev->revision); - fprintf(stderr, "bus 0x%04x cardnum 0x%02x function 0x%02x:" - " vendor 0x%04x device 0x%04x\n", - dev->bus, - dev->dev, - dev->func, - dev->vendor_id, - dev->device_id); - - if (0x8086 == dev->vendor_id) { // intel gpu support vaapi, vendor_id :0x8086 can find from file pci.ids - //Iris Xe Graphics don't suppot vaapi, so softening coding. - if (dev->device_id == 0xa7a0) { - is_vaapi = HW_VAAPI_FAIL1; - return NULL; - } - fprintf(stderr, "try this gpu card for vaapi\n"); - break; - } - } - } - pci_system_cleanup(); + } //assertions assert(encoder_ctx != NULL); @@ -2233,6 +2336,7 @@ static encoder_video_context_t *encoder_video_init_vaapi(encoder_context_t *enco if (!video_defaults) { fprintf(stderr, "ENCODER: defaults for video codec index %i not found: using raw (direct input)\n", encoder_ctx->video_codec_ind); is_vaapi = HW_VAAPI_FAIL1; + return NULL; } encoder_video_context_t *enc_video_ctx = calloc(1, sizeof(encoder_video_context_t)); @@ -2636,10 +2740,11 @@ int encoder_encode_video_vaapi(encoder_context_t *encoder_ctx, void *input_frame enc_video_ctx->flags = pkt->flags; enc_video_ctx->duration = pkt->duration; - if (pkt->size <= enc_video_ctx->outbuf_size) - memcpy(enc_video_ctx->outbuf, pkt->data, pkt->size); - else - fprintf(stderr, "video packet size is bigger than output buffer(%i>%i)\n", pkt->size, enc_video_ctx->outbuf_size); + if (!resize_outbuf_if_needed(enc_video_ctx, encoder_ctx, pkt)) { + last_video_pts = enc_video_ctx->pts; + getLoadLibsInstance()->m_av_packet_unref(pkt); + continue; + } /* free any side data since we cannot return it */ if (pkt->side_data_elems > 0) { diff --git a/libcam/libcam_encoder/gviewencoder.h b/libcam/libcam_encoder/gviewencoder.h index 714b33b33..1f94a1f6e 100644 --- a/libcam/libcam_encoder/gviewencoder.h +++ b/libcam/libcam_encoder/gviewencoder.h @@ -183,6 +183,7 @@ typedef struct _encoder_audio_context_t { int outbuf_coded_size; int64_t pts; + int64_t original_pts; /* 纳秒时间戳,不被编码器覆盖,用于 muxer PTS 计算 */ int64_t dts; int flags; int duration; diff --git a/libcam/libcam_encoder/mp4.c b/libcam/libcam_encoder/mp4.c index f4ad06964..486608f1d 100644 --- a/libcam/libcam_encoder/mp4.c +++ b/libcam/libcam_encoder/mp4.c @@ -21,12 +21,21 @@ #include "mp4.h" #include +#include "camview.h" #include "load_libs.h" +#include // 为abs函数添加头文件 + +extern int verbosity; // 添加verbosity声明 static int64_t video_pts = 0; static int64_t audio_pts = 0; static int64_t first_pts = 0; static int64_t last_pts = 0; +static int64_t last_video_timestamp = 0; // 添加上一帧视频时间戳记录 + +// 添加同步检查变量 +static int frame_count = 0; +static int64_t last_sync_check_time = 0; AVFormatContext *mp4_create_context(const char *filename) { @@ -121,18 +130,67 @@ int mp4_write_packet( getAvformat()->m_av_write_frame(mp4_ctx, outpacket); set_video_time_capture((double)(pts)/1000/1000000); } else { - fprintf(stderr,"pts err:video_pts: %d last_pts: %d\n", video_pts, last_pts); + fprintf(stderr,"pts err:video_pts: %ld last_pts: %ld\n", video_pts, last_pts); } last_pts = video_pts; if (first_pts == 0) { first_pts = pts; + last_video_timestamp = pts; + video_pts = 0; // 第一个视频包的PTS为0 } else { - video_pts = (double)(pts - first_pts) / 1000000.0 / 33.0; + // 使用真实时间戳计算,而不是硬编码的33ms + // 将纳秒时间戳转换为帧数(基于time_base) + int64_t timestamp_diff = pts - first_pts; // 从开始录制的时间差(纳秒) + + // 根据编码器的time_base计算PTS + // time_base通常是1/fps,所以pts = 时间差 * fps + if (time_base.den > 0 && time_base.num > 0) { + video_pts = (timestamp_diff * time_base.den) / (time_base.num * 1000000000LL); + } else { + // 如果time_base无效,使用动态帧率计算 + int64_t frame_duration = pts - last_video_timestamp; + if (frame_duration > 0) { + // 基于实际帧间隔计算 + video_pts = timestamp_diff / frame_duration; + } else { + // 回退到固定帧率(但使用更精确的计算) + video_pts = timestamp_diff / 33333333LL; // 30fps = 33.33ms + } + } + + last_video_timestamp = pts; + if (video_pts == last_pts) //pts must be strictly incremented video_pts++; if(video_pts < last_pts) video_pts = last_pts + 1; + + if (verbosity > 2) { + fprintf(stderr, "video_pts: %ld (timestamp: %ld, first_pts: %ld)\n", + video_pts, pts, first_pts); + } + + // 每100帧检查一次音视频同步状态 + frame_count++; + if (frame_count % 100 == 0) { + int64_t video_time_ms = (pts - first_pts) / 1000000; // 视频实际时间(毫秒) + int64_t video_pts_time_ms = 0; + + // 根据time_base计算视频PTS对应的时间 + if (time_base.den > 0 && time_base.num > 0) { + video_pts_time_ms = (video_pts * time_base.num * 1000) / time_base.den; + } + + int64_t sync_diff = video_time_ms - video_pts_time_ms; + + if (verbosity > 0 && abs(sync_diff) > 100) { // 如果差异超过100ms + fprintf(stderr, "SYNC CHECK: Frame %d, Real time: %ldms, PTS time: %ldms, Diff: %ldms\n", + frame_count, video_time_ms, video_pts_time_ms, sync_diff); + } + + last_sync_check_time = pts; + } } } @@ -147,9 +205,31 @@ int mp4_write_packet( getLoadLibsInstance()->m_av_packet_rescale_ts(outpacket, time_base, audio_time); getAvformat()->m_av_write_frame(mp4_ctx, outpacket); - //AAC音频标准是1024 - //MP3音频标准是1152 - audio_pts+= 1024; + // 使用基于真实时间的音频PTS计算 + if (first_pts == 0) { + first_pts = pts; // 设置first_pts + audio_pts = 0; // 第一个音频包的PTS为0 + } else { + // 将纳秒时间戳转换为音频时间基准 + int64_t timestamp_diff = pts - first_pts; // 从开始录制的时间差(纳秒) + + if (time_base.den > 0 && time_base.num > 0) { + // 根据音频编码器的time_base计算PTS + audio_pts = (timestamp_diff * time_base.den) / (time_base.num * 1000000000LL); + } else { + // 如果time_base无效,使用采样率计算 + int sample_rate = codec_data->codec_context->sample_rate; + if (sample_rate <= 0) sample_rate = 48000; // 默认采样率 + + // 基于真实时间和采样率计算音频PTS + audio_pts = (timestamp_diff * sample_rate) / 1000000000LL; + } + } + + if (verbosity > 2) { + fprintf(stderr, "audio_pts: %ld (timestamp: %ld, first_pts: %ld)\n", + audio_pts, pts, first_pts); + } } if(outpacket->data){ @@ -166,6 +246,10 @@ void mp4_destroy_context(AVFormatContext *mp4_ctx) video_pts = 0; audio_pts = 0; first_pts = 0; + last_pts = 0; + last_video_timestamp = 0; // 重置新增的变量 + frame_count = 0; // 重置帧计数 + last_sync_check_time = 0; // 重置同步检查时间 if(mp4_ctx != NULL) { getAvformat()->m_avformat_free_context(mp4_ctx); diff --git a/libcam/libcam_encoder/muxer.c b/libcam/libcam_encoder/muxer.c index 74b5aa9fe..08c5ff8ed 100644 --- a/libcam/libcam_encoder/muxer.c +++ b/libcam/libcam_encoder/muxer.c @@ -218,7 +218,7 @@ int encoder_write_audio_data(encoder_context_t *encoder_ctx) 1, enc_audio_ctx->outbuf, (uint32_t)enc_audio_ctx->outbuf_coded_size, - (uint64_t)enc_audio_ctx->pts, + (uint64_t)enc_audio_ctx->original_pts, enc_audio_ctx->flags); break; @@ -263,6 +263,10 @@ void encoder_muxer_init(encoder_context_t *encoder_ctx, const char *filename) assert(encoder_ctx->enc_video_ctx != NULL); encoder_codec_data_t *video_codec_data = (encoder_codec_data_t *) encoder_ctx->enc_video_ctx->codec_data; + if(!video_codec_data) { + printf("deepin-camera: video_codec_data (encoder_ctx->enc_video_ctx->codec_data) is null\n"); + return; + } int video_codec_id = AV_CODEC_ID_NONE; diff --git a/libcam/libcam_v4l2core/frame_decoder.c b/libcam/libcam_v4l2core/frame_decoder.c index 5e4e01501..a5845b13d 100644 --- a/libcam/libcam_v4l2core/frame_decoder.c +++ b/libcam/libcam_v4l2core/frame_decoder.c @@ -99,7 +99,7 @@ int alloc_v4l2_frames(v4l2_dev_t *vd) exit(-1); } - vd->frame_queue[i].yuv_frame = calloc((uint8_t)framesizeIn, sizeof(uint8_t)); + vd->frame_queue[i].yuv_frame = calloc((size_t) framesizeIn, sizeof(uint8_t)); if(vd->frame_queue[i].yuv_frame == NULL) { fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (alloc_v4l2_frames): %s\n", strerror(errno)); @@ -108,7 +108,7 @@ int alloc_v4l2_frames(v4l2_dev_t *vd) } - vd->h264_last_IDR = calloc((uint8_t)(width * height), sizeof(uint8_t)); + vd->h264_last_IDR = calloc((size_t) width * (size_t) height, sizeof(uint8_t)); if(vd->h264_last_IDR == NULL) { fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (alloc_v4l2_frames): %s\n", strerror(errno)); @@ -134,6 +134,7 @@ int alloc_v4l2_frames(v4l2_dev_t *vd) /*frame queue*/ for(i=0; iframe_queue_size; ++i) { + vd->frame_queue[i].yuv_frame_max_size = (size_t) framesizeIn; vd->frame_queue[i].yuv_frame = calloc((size_t) framesizeIn, sizeof(uint8_t)); if(vd->frame_queue[i].yuv_frame == NULL) { @@ -346,6 +347,7 @@ void clean_v4l2_frames(v4l2_dev_t *vd) free(vd->frame_queue[i].yuv_frame); vd->frame_queue[i].yuv_frame = NULL; } + vd->frame_queue[i].yuv_frame_max_size = 0; } if(vd->h264_last_IDR) @@ -828,6 +830,44 @@ int decode_v4l2_frame(v4l2_dev_t *vd, v4l2_frame_buff_t *frame) //} if(verbosity > 3) fprintf(stderr, "V4L2_CORE: (jpeg decoder) decode frame of size %i\n", ret); + if (ret >= 0) + { + int real_w = 0; + int real_h = 0; + if (jpeg_get_decoded_size(&real_w, &real_h) == 0 && + real_w > 0 && real_h > 0 && + (real_w != frame->width || real_h != frame->height)) + { + if(verbosity > 0) + fprintf(stderr, "V4L2_CORE: (jpeg decoder) frame size %dx%d != negotiated %dx%d, using real size\n", + real_w, real_h, frame->width, frame->height); + frame->width = real_w; + frame->height = real_h; + } + + if (real_w > 0 && real_h > 0) + { + size_t need = (size_t)real_w * (size_t)real_h * 3 / 2; + if (need > 0 && need != frame->yuv_frame_max_size) + { + uint8_t *p = (uint8_t *)realloc(frame->yuv_frame, need); + if (p) + { + frame->yuv_frame = p; + frame->yuv_frame_max_size = need; + if(verbosity > 0) + fprintf(stderr, "V4L2_CORE: (jpeg decoder) yuv_frame trimmed %dx%d (%zu bytes)\n", + real_w, real_h, need); + } + else + { + if(verbosity > 0) + fprintf(stderr, "V4L2_CORE: (jpeg decoder) realloc failed for yuv_frame, keep old size\n"); + return E_ALLOC_ERR; + } + } + } + } ret = E_OK; break; diff --git a/libcam/libcam_v4l2core/gui.c b/libcam/libcam_v4l2core/gui.c index 2c5dd5d9a..c742c3527 100644 --- a/libcam/libcam_v4l2core/gui.c +++ b/libcam/libcam_v4l2core/gui.c @@ -690,7 +690,7 @@ void set_photo_name(const char *name) fprintf(stderr, "deepin-camera: no valid file extension for image file %s\n", name); - fprintf(stderr, "deepin-camera: using format %i\n", get_photo_format()); + fprintf(stderr, "deepin-camera: using format %i (1:IMG_FMT_JPG, 2:IMG_FMT_PNG, 3:IMG_FMT_BMP)\n", get_photo_format()); switch(get_photo_format()) { case IMG_FMT_JPG: diff --git a/libcam/libcam_v4l2core/gviewv4l2core.h b/libcam/libcam_v4l2core/gviewv4l2core.h index 7d8c7098a..85a0640ae 100644 --- a/libcam/libcam_v4l2core/gviewv4l2core.h +++ b/libcam/libcam_v4l2core/gviewv4l2core.h @@ -256,6 +256,7 @@ typedef struct _v4l2_frame_buff_t { uint64_t timestamp; // captured frame timestamp uint8_t *raw_frame; // pointer to raw frame + size_t yuv_frame_max_size; //maximum size for decoded yuv frame (bytes); MJPG is trimmed to real decoded size after 1st frame uint8_t *yuv_frame; // pointer to decoded yuv frame uint8_t *h264_frame; // pointer to regular or demultiplexed h264 frame uint8_t *tmp_buffer; //temporary buffer used in decoding diff --git a/libcam/libcam_v4l2core/jpeg_decoder.c b/libcam/libcam_v4l2core/jpeg_decoder.c index 072b96169..e3c8b1ccc 100644 --- a/libcam/libcam_v4l2core/jpeg_decoder.c +++ b/libcam/libcam_v4l2core/jpeg_decoder.c @@ -37,6 +37,7 @@ #include #include +#include "camview.h" #include "gviewv4l2core.h" #include "colorspaces.h" #include "frame_decoder.h" @@ -1356,9 +1357,11 @@ void jpeg_close_decoder() typedef struct _codec_data_t { - AVCodec *codec; - AVCodecContext *context; - AVFrame *picture; + AVCodec *codec; + AVCodecContext *context; + AVFrame *picture; + AVBufferRef *hw_device_ctx; // hardware device context + enum AVPixelFormat hw_pix_fmt; // hardware pixel format } codec_data_t; /* @@ -1378,73 +1381,112 @@ int jpeg_init_decoder(int width, int height) getLoadLibsInstance()->m_avcodec_init(); #endif #if !LIBAVCODEC_VER_AT_LEAST(58,9) - /* - * register all the codecs (we can also register only the codec - * we wish to have smaller code) - */ + /* + * register all the codecs (we can also register only the codec + * we wish to have smaller code) + */ getLoadLibsInstance()->m_avcodec_register_all(); #endif getAvutil()->m_av_log_set_level(AV_LOG_PANIC); - if(jpeg_ctx != NULL) - jpeg_close_decoder(); + if(jpeg_ctx != NULL) + jpeg_close_decoder(); - jpeg_ctx = calloc(1, sizeof(jpeg_decoder_context_t)); - if(jpeg_ctx == NULL) - { - fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); - exit(-1); - } + jpeg_ctx = calloc(1, sizeof(jpeg_decoder_context_t)); + if(jpeg_ctx == NULL) + { + fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); + exit(-1); + } - codec_data_t *codec_data = calloc(1, sizeof(codec_data_t)); - if(codec_data == NULL) - { - fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); - exit(-1); - } + codec_data_t *codec_data = calloc(1, sizeof(codec_data_t)); + if(codec_data == NULL) + { + fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); + exit(-1); + } codec_data->codec = getLoadLibsInstance()->m_avcodec_find_decoder(AV_CODEC_ID_MJPEG); - if(!codec_data->codec) - { - fprintf(stderr, "V4L2_CORE: (mjpeg decoder) codec not found\n"); - free(jpeg_ctx); - free(codec_data); - jpeg_ctx = NULL; - return E_NO_CODEC; - } + if(!codec_data->codec) + { + fprintf(stderr, "V4L2_CORE: (mjpeg decoder) codec not found\n"); + free(jpeg_ctx); + free(codec_data); + jpeg_ctx = NULL; + return E_NO_CODEC; + } #if LIBAVCODEC_VER_AT_LEAST(53,6) codec_data->context = getLoadLibsInstance()->m_avcodec_alloc_context3(codec_data->codec); - getLoadLibsInstance()->m_avcodec_get_context_defaults3 && getLoadLibsInstance()->m_avcodec_get_context_defaults3 (codec_data->context, codec_data->codec); + getLoadLibsInstance()->m_avcodec_get_context_defaults3 && getLoadLibsInstance()->m_avcodec_get_context_defaults3 (codec_data->context, codec_data->codec); #else - codec_data->context = getLoadLibsInstance()->m_avcodec_alloc_context(); - getLoadLibsInstance()->m_avcodec_get_context_defaults(codec_data->context); + codec_data->dec_ctx = getLoadLibsInstance()->m_avcodec_alloc_context(); + getLoadLibsInstance()->m_avcodec_get_context_defaults(codec_data->dec_ctx); #endif - if(codec_data->context == NULL) - { - fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (h264_init_decoder): %s\n", strerror(errno)); - exit(-1); - } - - codec_data->context->pix_fmt = AV_PIX_FMT_YUV422P; - codec_data->context->width = width; - codec_data->context->height = height; - //jpeg_ctx->context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); + if(codec_data->context == NULL) + { + fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (h264_init_decoder): %s\n", strerror(errno)); + exit(-1); + } + + codec_data->context->pix_fmt = AV_PIX_FMT_YUV422P; + //jpeg_ctx->context->dsp_mask = (FF_MM_MMX | FF_MM_MMXEXT | FF_MM_SSE); + + // Initialize hardware device context (VA-API) + if (is_driver_available(get_libva_driver_name())) { + int ret = getAvutil()->m_av_hwdevice_ctx_create(&codec_data->hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, NULL, NULL, 0); + if (ret < 0) { + fprintf(stderr, "V4L2_CORE: Unable to create VA-API hardware context\n"); + // If hardware decoding initialization fails, continue using software decoding + codec_data->hw_device_ctx = NULL; + } else { + codec_data->context->hw_device_ctx = getAvutil()->m_av_buffer_ref(codec_data->hw_device_ctx); + + // Check whether the decoder supports JPEG hardware acceleration + int ret = -1; + for (int i = 0;; i++) { + const AVCodecHWConfig *config = getLoadLibsInstance()->m_avcodec_get_hw_config(codec_data->codec, i); + if (!config) break; + + if (config->device_type == AV_HWDEVICE_TYPE_VAAPI && + config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) { + codec_data->hw_pix_fmt = config->pix_fmt; + ret = 0; + fprintf(stderr, "V4L2_CORE: (mjpeg decoder) Find supported VA-API hardware acceleration configurations\n"); + break; + } + } + if (ret < 0) { + fprintf(stderr, "V4L2_CORE: (mjpeg decoder) The decoder does not support VA-API JPEG hardware acceleration and will use software decoding\n"); + // Cleaning up hardware device context + if (codec_data->hw_device_ctx) { + getAvutil()->m_av_buffer_unref(&codec_data->hw_device_ctx); + codec_data->hw_device_ctx = NULL; + } + } else { + fprintf(stderr, "V4L2_CORE: (mjpeg decoder) Use VA-API hardware acceleration, pixel format: %s\n", + getAvutil()->m_av_get_pix_fmt_name(codec_data->hw_pix_fmt)); + } + } + } #if LIBAVCODEC_VER_AT_LEAST(53,6) if (getLoadLibsInstance()->m_avcodec_open2(codec_data->context, codec_data->codec, NULL) < 0) #else - if (getLoadLibsInstance()->m_avcodec_open(codec_data->context, codec_data->codec) < 0) + if (getLoadLibsInstance()->m_avcodec_open(codec_data->dec_ctx, codec_data->codec) < 0) #endif - { - fprintf(stderr, "V4L2_CORE: (mjpeg decoder) couldn't open codec\n"); + { + fprintf(stderr, "V4L2_CORE: (mjpeg decoder) couldn't open codec\n"); + if (codec_data->hw_device_ctx) { + getAvutil()->m_av_buffer_unref(&codec_data->hw_device_ctx); + } getLoadLibsInstance()->m_avcodec_close(codec_data->context); - free(codec_data->context); - free(codec_data); - free(jpeg_ctx); - jpeg_ctx = NULL; - return E_NO_CODEC; - } + free(codec_data->context); + free(codec_data); + free(jpeg_ctx); + jpeg_ctx = NULL; + return E_NO_CODEC; + } #if LIBAVCODEC_VER_AT_LEAST(55,28) codec_data->picture = getAvutil()->m_av_frame_alloc(); @@ -1454,23 +1496,23 @@ int jpeg_init_decoder(int width, int height) getLoadLibsInstance()->m_avcodec_get_frame_defaults(codec_data->picture); #endif - /*alloc temp buffer*/ + /*alloc temp buffer*/ jpeg_ctx->tmp_frame = calloc((size_t)(width*height*2), sizeof(uint8_t)); - if(jpeg_ctx->tmp_frame == NULL) - { - fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); - exit(-1); - } + if(jpeg_ctx->tmp_frame == NULL) + { + fprintf(stderr, "V4L2_CORE: FATAL memory allocation failure (jpeg_init_decoder): %s\n", strerror(errno)); + exit(-1); + } #if LIBAVUTIL_VER_AT_LEAST(54,6) jpeg_ctx->pic_size = getAvutil()->m_av_image_get_buffer_size(codec_data->context->pix_fmt, width, height, 1); #else - jpeg_ctx->pic_size = avpicture_get_size(codec_data->context->pix_fmt, width, height); + jpeg_ctx->pic_size = avpicture_get_size(codec_data->dec_ctx->pix_fmt, width, height); #endif - jpeg_ctx->width = width; - jpeg_ctx->height = height; - jpeg_ctx->codec_data = codec_data; + jpeg_ctx->width = width; + jpeg_ctx->height = height; + jpeg_ctx->codec_data = codec_data; - return E_OK; + return E_OK; } /* @@ -1489,39 +1531,124 @@ int jpeg_init_decoder(int width, int height) */ int jpeg_decode(uint8_t *out_buf, uint8_t *in_buf, int size) { - /*asserts*/ - assert(jpeg_ctx != NULL); - assert(in_buf != NULL); - assert(out_buf != NULL); + /*asserts*/ + assert(jpeg_ctx != NULL); + assert(in_buf != NULL); + assert(out_buf != NULL); - AVPacket avpkt; + AVPacket avpkt; getLoadLibsInstance()->m_av_init_packet(&avpkt); - avpkt.size = size; - avpkt.data = in_buf; - - codec_data_t *codec_data = (codec_data_t *) jpeg_ctx->codec_data; - - int got_frame = 0; - int ret = libav_decode(codec_data->context, codec_data->picture, &got_frame, &avpkt); - - if(ret < 0) - { - fprintf(stderr, "V4L2_CORE: (jpeg decoder) error while decoding frame\n"); - return ret; - } - - if(got_frame) - { + avpkt.size = size; + avpkt.data = in_buf; + + codec_data_t *codec_data = (codec_data_t *) jpeg_ctx->codec_data; + + int got_frame = 0; + int ret = libav_decode(codec_data->context, codec_data->picture, &got_frame, &avpkt); + + if(ret < 0) + { + fprintf(stderr, "V4L2_CORE: (jpeg decoder) error while decoding frame, code(%d)\n", ret); + return ret; + } + + if(got_frame) + { + // check use hwdec work ok + if (codec_data->hw_device_ctx && codec_data->picture->format == codec_data->hw_pix_fmt) { + AVFrame *sw_frame = getAvutil()->m_av_frame_alloc(); + if (!sw_frame) { + fprintf(stderr, "V4L2_CORE: (jpeg decoder) Failed to allocate software frame\n"); + return -1; + } + + // Transfer hardware frame data to system memory + if (getAvutil()->m_av_hwframe_transfer_data(sw_frame, codec_data->picture, 0) < 0) { + fprintf(stderr, "V4L2_CORE: (jpeg decoder) Failed to transfer hardware frame data\n"); + getAvutil()->m_av_frame_free(&sw_frame); + return -1; + } + if (verbosity > 0) { + printf(" Width: %d pixels\n", sw_frame->width); + printf(" Height: %d pixels\n", sw_frame->height); + printf(" Pixel format: %s\n", getAvutil()->m_av_get_pix_fmt_name((enum AVPixelFormat)sw_frame->format)); + printf(" Hardware pixel format: %s\n", getAvutil()->m_av_get_pix_fmt_name(codec_data->hw_pix_fmt)); + printf(" Linesize[0]: %d\n", sw_frame->linesize[0]); + printf("-------------------------\n"); + printf(" - %s\n", getAvutil()->m_av_get_pix_fmt_name((enum AVPixelFormat)AV_PIX_FMT_YUV420P)); + } #if LIBAVUTIL_VER_AT_LEAST(54,6) - getAvutil()->m_av_image_copy_to_buffer(jpeg_ctx->tmp_frame, jpeg_ctx->pic_size, - (const uint8_t * const*) codec_data->picture->data, codec_data->picture->linesize, - codec_data->context->pix_fmt, jpeg_ctx->width, jpeg_ctx->height, 1); + { + int dec_w = sw_frame->width; + int dec_h = sw_frame->height; + int need = getAvutil()->m_av_image_get_buffer_size(sw_frame->format, dec_w, dec_h, 1); + if (need > 0 && (size_t)need > (size_t)jpeg_ctx->pic_size) { + uint8_t *p = realloc(jpeg_ctx->tmp_frame, (size_t)need); + if (!p) { + fprintf(stderr, "V4L2_CORE: (jpeg decoder) realloc failed for tmp_frame\n"); + getAvutil()->m_av_frame_free(&sw_frame); + return -1; + } + jpeg_ctx->tmp_frame = p; + jpeg_ctx->pic_size = need; + } + jpeg_ctx->width = dec_w; + jpeg_ctx->height = dec_h; + getAvutil()->m_av_image_copy_to_buffer(jpeg_ctx->tmp_frame, jpeg_ctx->pic_size, + (const uint8_t * const*) sw_frame->data, sw_frame->linesize, + sw_frame->format, dec_w, dec_h, 1); + } + if (sw_frame->format == AV_PIX_FMT_NV12) { + nv12_to_yu12(out_buf, jpeg_ctx->tmp_frame, jpeg_ctx->width, jpeg_ctx->height); + getAvutil()->m_av_frame_free(&sw_frame); + return jpeg_ctx->pic_size; + } #else - avpicture_layout((AVPicture *) codec_data->picture, codec_data->context->pix_fmt, - jpeg_ctx->width, jpeg_ctx->height, jpeg_ctx->tmp_frame, jpeg_ctx->pic_size); + avpicture_layout((AVPicture *) sw_frame, sw_frame->format, + jpeg_ctx->width, jpeg_ctx->height, jpeg_ctx->tmp_frame, jpeg_ctx->pic_size); #endif + getAvutil()->m_av_frame_free(&sw_frame); + } else { + // Software decoding: Use decoded frames directly +#if LIBAVUTIL_VER_AT_LEAST(54,6) + static int decodeCount = 0; + if (verbosity > 0) { + if (decodeCount % LOGGING_INTERVAL == LOGGING_INTERVAL - 1) { + printf("codec_data size(%dx%d) linesize(%d) format(%s) jpeg_ctx size(%dx%d) pic_size(%d)\n", codec_data->picture->width, codec_data->picture->height, + codec_data->picture->linesize[0], + getAvutil()->m_av_get_pix_fmt_name((enum AVPixelFormat)codec_data->picture->format), + jpeg_ctx->width, jpeg_ctx->height, jpeg_ctx->pic_size); + decodeCount = 0; + } + decodeCount++; + } + { + int dec_w = codec_data->picture->width; + int dec_h = codec_data->picture->height; + enum AVPixelFormat dec_fmt = codec_data->picture->format; + int need = getAvutil()->m_av_image_get_buffer_size(dec_fmt, dec_w, dec_h, 1); + if (need > 0 && (size_t)need > (size_t)jpeg_ctx->pic_size) { + uint8_t *p = realloc(jpeg_ctx->tmp_frame, (size_t)need); + if (!p) { + fprintf(stderr, "V4L2_CORE: (jpeg decoder) realloc failed for tmp_frame\n"); + return -1; + } + jpeg_ctx->tmp_frame = p; + jpeg_ctx->pic_size = need; + } + jpeg_ctx->width = dec_w; + jpeg_ctx->height = dec_h; + getAvutil()->m_av_image_copy_to_buffer(jpeg_ctx->tmp_frame, jpeg_ctx->pic_size, + (const uint8_t * const*) codec_data->picture->data, codec_data->picture->linesize, + dec_fmt, dec_w, dec_h, 1); + } +#else + avpicture_layout((AVPicture *) codec_data->picture, codec_data->dec_ctx->pix_fmt, + jpeg_ctx->width, jpeg_ctx->height, jpeg_ctx->tmp_frame, jpeg_ctx->pic_size); +#endif + } if (codec_data->context->pix_fmt == AV_PIX_FMT_YUVJ422P) { /* libavcodec output is in yuvj422p */ @@ -1539,12 +1666,33 @@ int jpeg_decode(uint8_t *out_buf, uint8_t *in_buf, int size) return jpeg_ctx->pic_size; }else return 0; - } - else - return 0; + } + else + return 0; } +/* + * get real (decoded) frame size + * args: + * out_w - pointer to receive real decoded width (may be NULL) + * out_h - pointer to receive real decoded height (may be NULL) + * + * asserts: + * none + * + * returns: 0 on success (valid size available); negative if not initialized + * or not decoded yet + */ +int jpeg_get_decoded_size(int *out_w, int *out_h) +{ + if (jpeg_ctx == NULL || jpeg_ctx->width <= 0 || jpeg_ctx->height <= 0) + return -1; + if (out_w) *out_w = jpeg_ctx->width; + if (out_h) *out_h = jpeg_ctx->height; + return 0; +} + /* * close (m)jpeg decoder context * args: @@ -1557,32 +1705,37 @@ int jpeg_decode(uint8_t *out_buf, uint8_t *in_buf, int size) */ void jpeg_close_decoder() { - if(jpeg_ctx == NULL) - return; + if(jpeg_ctx == NULL) + return; - codec_data_t *codec_data = (codec_data_t *) jpeg_ctx->codec_data; + codec_data_t *codec_data = (codec_data_t *) jpeg_ctx->codec_data; getLoadLibsInstance()->m_avcodec_close(codec_data->context); - free(codec_data->context); + free(codec_data->context); #if LIBAVCODEC_VER_AT_LEAST(55,28) getAvutil()->m_av_frame_free(&codec_data->picture); #else - #if LIBAVCODEC_VER_AT_LEAST(54,28) - getLoadLibsInstance()->m_avcodec_free_frame(&codec_data->picture); - #else - av_freep(&codec_data->picture); - #endif +#if LIBAVCODEC_VER_AT_LEAST(54,28) + getLoadLibsInstance()->m_avcodec_free_frame(&codec_data->picture); +#else + av_freep(&codec_data->picture); +#endif #endif - if(jpeg_ctx->tmp_frame) - free(jpeg_ctx->tmp_frame); + // Release hardware device context + if (codec_data->hw_device_ctx) { + getAvutil()->m_av_buffer_unref(&codec_data->hw_device_ctx); + } - free(codec_data); - free(jpeg_ctx); + if(jpeg_ctx->tmp_frame) + free(jpeg_ctx->tmp_frame); - jpeg_ctx = NULL; + free(codec_data); + free(jpeg_ctx); + + jpeg_ctx = NULL; } #endif diff --git a/libcam/libcam_v4l2core/jpeg_decoder.h b/libcam/libcam_v4l2core/jpeg_decoder.h index f3fa81029..f9aa7cedc 100644 --- a/libcam/libcam_v4l2core/jpeg_decoder.h +++ b/libcam/libcam_v4l2core/jpeg_decoder.h @@ -75,6 +75,16 @@ int jpeg_init_decoder(int width, int height); */ int jpeg_decode(uint8_t *out_buf, uint8_t *in_buf, int size); +/* + * get real (decoded) frame size after jpeg_decode() + * args: + * out_w - pointer to receive real decoded width (may be NULL) + * out_h - pointer to receive real decoded height (may be NULL) + * + * returns: 0 - OK; negative - not initialized or not decoded yet + */ +int jpeg_get_decoded_size(int *out_w, int *out_h); + /* * close (m)jpeg decoder context * args: diff --git a/libcam/libcam_v4l2core/v4l2_controls.c b/libcam/libcam_v4l2core/v4l2_controls.c index bfb001d9b..1f7208528 100644 --- a/libcam/libcam_v4l2core/v4l2_controls.c +++ b/libcam/libcam_v4l2core/v4l2_controls.c @@ -180,32 +180,24 @@ static void print_control(v4l2_ctrl_t *control, int i) printf("\tmin:%d max:%d step:%d def:%d curr:%d\n", control->control.minimum, control->control.maximum, control->control.step, control->control.default_value, control->value); - free(control->name); - control->name=NULL; break; case V4L2_CTRL_TYPE_INTEGER64: printf("control[%d]:(int64) 0x%x '%s'\n",i ,control->control.id, control->name); printf ("\tcurr:%" PRId64 "\n", control->value64); - free(control->name); - control->name=NULL; break; case V4L2_CTRL_TYPE_STRING: printf("control[%d]:(str) 0x%x '%s'\n",i ,control->control.id, control->name); printf ("\tmin:%d max:%d step:%d curr: %s\n", control->control.minimum, control->control.maximum, - control->control.step, control->string); - free(control->name); - control->name=NULL; + control->control.step, control->string); break; case V4L2_CTRL_TYPE_BOOLEAN: printf("control[%d]:(bool) 0x%x '%s'\n",i ,control->control.id, control->name); printf ("\tdef:%d curr:%d\n", - control->control.default_value, control->value); - free(control->name); - control->name=NULL; + control->control.default_value, control->value); break; case V4L2_CTRL_TYPE_MENU: @@ -213,10 +205,8 @@ static void print_control(v4l2_ctrl_t *control, int i) printf("\tmin:%d max:%d def:%d curr:%d\n", control->control.minimum, control->control.maximum, control->control.default_value, control->value); - for (j = 0; (__s32)control->menu[j].index <= control->control.maximum; j++) + for (j = 0; (__s32)control->menu[j].index <= control->control.maximum; j++) printf("\tmenu[%d]: [%d] -> '%s'\n", j, control->menu[j].index, control->menu_entry[j]); - free(control->name); - control->name=NULL; break; case V4L2_CTRL_TYPE_INTEGER_MENU: @@ -224,18 +214,14 @@ static void print_control(v4l2_ctrl_t *control, int i) printf("\tmin:%d max:%d def:%d curr:%d\n", control->control.minimum, control->control.maximum, control->control.default_value, control->value); - for (j = 0; (__s32)control->menu[j].index <= control->control.maximum; j++) + for (j = 0; (__s32)control->menu[j].index <= control->control.maximum; j++) printf("\tmenu[%d]: [%d] -> %" PRId64 " (0x%" PRIx64 ")\n", j, control->menu[j].index, (int64_t) control->menu[j].value, (int64_t) control->menu[j].value); - free(control->name); - control->name=NULL; - break; + break; case V4L2_CTRL_TYPE_BUTTON: printf("control[%d]:(button) 0x%x '%s'\n",i ,control->control.id, control->name); - free(control->name); - control->name=NULL; break; case V4L2_CTRL_TYPE_BITMASK: @@ -249,8 +235,6 @@ static void print_control(v4l2_ctrl_t *control, int i) default: printf("control[%d]:(unknown - 0x%x) 0x%x '%s'\n",i ,control->control.type, control->control.id, control->control.name); - free(control->name); - control->name=NULL; break; } } diff --git a/libcam/libcam_v4l2core/v4l2_core.c b/libcam/libcam_v4l2core/v4l2_core.c index 2824ddfad..4da187c32 100644 --- a/libcam/libcam_v4l2core/v4l2_core.c +++ b/libcam/libcam_v4l2core/v4l2_core.c @@ -1115,7 +1115,7 @@ static int process_input_buffer(v4l2_dev_t *vd) * driver timestamp is unreliable * use monotonic system time */ - vd->frame_queue[qind].timestamp = ns_time_monotonic(); + vd->frame_queue[qind].timestamp = ns_time_monotonic(); vd->frame_queue[qind].index = (int)vd->buf.index; @@ -1696,18 +1696,50 @@ void v4l2core_prepare_valid_resolution(v4l2_dev_t *vd) } } - for(int i=0; i < vd->list_stream_formats[format_index].numb_res; i++) - { - if( my_width <= vd->list_stream_formats[format_index].list_stream_cap[i].width && - my_height <= vd->list_stream_formats[format_index].list_stream_cap[i].height && - ((vd->list_stream_formats[format_index].list_stream_cap[i].width % 8) == 0 - && (vd->list_stream_formats[format_index].list_stream_cap[i].width % 16) == 0 - && (vd->list_stream_formats[format_index].list_stream_cap[i].height % 8) == 0)) - { - my_width = vd->list_stream_formats[format_index].list_stream_cap[i].width; - my_height = vd->list_stream_formats[format_index].list_stream_cap[i].height; - } - } + int found_preferred = 0; // 是否找到首选分辨率 + int pref_w = get_preferred_resolution_width(); + int pref_h = get_preferred_resolution_height(); + // 最大分辨率 + int max_width = 0; + int max_height = 0; + // 通过一次遍历,找出首选分辨率,同时找出最大分辨率。虽然最大分辨率可能用不到,但是便于调试分析 + for (int i = 0; i < vd->list_stream_formats[format_index].numb_res; i++) + { + int w = vd->list_stream_formats[format_index].list_stream_cap[i].width; + int h = vd->list_stream_formats[format_index].list_stream_cap[i].height; + if (is_valid_resolution(w, h)) + { + // 找出首选分辨率 + printf("V4L2_CORE: check valid resolution, width: %d, height: %d, preferred width: %d, preferred height: %d\n", + w, h, pref_w, pref_h); + if (!found_preferred && pref_w != 0 && pref_h != 0 && w == pref_w && h == pref_h) { + printf("V4L2_CORE: preferred resolution found, width: %d, height: %d\n", w, h); + found_preferred = 1; + } + + // 找出最大分辨率 + if (w > max_width || (w == max_width && h > max_height)) { + max_width = w; + max_height = h; + } + } + } + printf("V4L2_CORE: max resolution found, width: %d, height: %d\n", max_width, max_height); + if (found_preferred) { + // 有首选分辨率,使用首选分辨率 + printf("V4L2_CORE: use preferred resolution, width: %d, height: %d\n", pref_w, pref_h); + my_width = pref_w; + my_height = pref_h; + } else { + // 没有首选分辨率,使用最大分辨率 + printf("V4L2_CORE: use max resolution, width: %d, height: %d\n", max_width, max_height); + my_width = max_width; + my_height = max_height; + // 极端情况,没有可用分辨率,发出错误提示 + if (max_width == 0 || max_height == 0) { + printf("V4L2_CORE: ERROR: no valid resolution found!\n"); + } + } } /* diff --git a/libcam/libcam_v4l2core/v4l2_core.h b/libcam/libcam_v4l2core/v4l2_core.h index 0d9ada9a5..de1bb3bd9 100644 --- a/libcam/libcam_v4l2core/v4l2_core.h +++ b/libcam/libcam_v4l2core/v4l2_core.h @@ -91,3 +91,8 @@ struct _v4l2_dev_t }; void /*__attribute__ ((constructor))*/ v4l2core_init(); #endif + +/* + * logging interval + */ +#define LOGGING_INTERVAL 5 diff --git a/libcam/libcam_v4l2core/v4l2_devices.c b/libcam/libcam_v4l2core/v4l2_devices.c index bb9c48a63..2c8a1c3fe 100644 --- a/libcam/libcam_v4l2core/v4l2_devices.c +++ b/libcam/libcam_v4l2core/v4l2_devices.c @@ -29,6 +29,7 @@ #include #include +#include "camview.h" #include "gviewv4l2core.h" #include "v4l2_devices.h" #include "cameraconfig.h" @@ -124,6 +125,123 @@ static void free_device_list() my_device_list.list_devices = NULL; } +/* + * 检查设备是否为标准摄像头设备 + * args: + * fd - 设备文件描述符 + * v4l2_cap - 设备能力结构体 + * device_path - 设备节点路径 (如 /dev/video0) + * + * returns: 1 if standard camera, 0 if not + */ +static int is_standard_camera_device(int fd, struct v4l2_capability *cap, const char *device_path) +{ + struct v4l2_fmtdesc fmt; + int has_standard_format = 0; + + // 1. 检查是否支持标准用户级视频格式 + memset(&fmt, 0, sizeof(fmt)); + fmt.index = 0; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + // 标准视频格式列表 + static const uint32_t standard_formats[] = { + V4L2_PIX_FMT_MJPEG, // Motion-JPEG + V4L2_PIX_FMT_YUYV, // YUYV 4:2:2 + V4L2_PIX_FMT_H264, // H.264 + V4L2_PIX_FMT_UYVY, // UYVY 4:2:2 + V4L2_PIX_FMT_NV12, // Y/UV 4:2:0 + V4L2_PIX_FMT_NV21, // Y/VU 4:2:0 + V4L2_PIX_FMT_YUV420 // YUV 4:2:0 + }; + static const int num_standard_formats = sizeof(standard_formats) / sizeof(standard_formats[0]); + + while (xioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) { + // 检查是否支持标准格式 + for (int i = 0; i < num_standard_formats; i++) { + if (fmt.pixelformat == standard_formats[i]) { + has_standard_format = 1; + break; + } + } + if (has_standard_format) { + break; // 找到标准格式后立即退出 + } + fmt.index++; + } + + // 2. 检查设备驱动和名称,过滤ISP设备 + static const char* isp_driver_keywords[] = { + "rkcif", "rkisp" + }; + static const char* isp_card_keywords[] = { + "rkisp", "rkcif", "statistics", "mainpath", "selfpath" + }; + static const int num_driver_keywords = sizeof(isp_driver_keywords) / sizeof(isp_driver_keywords[0]); + static const int num_card_keywords = sizeof(isp_card_keywords) / sizeof(isp_card_keywords[0]); + + int is_isp_device = 0; + + // 检查驱动名称 + for (int i = 0; i < num_driver_keywords && !is_isp_device; i++) { + if (strstr((char *)cap->driver, isp_driver_keywords[i]) != NULL) { + is_isp_device = 1; + } + } + + // 检查card名称 + for (int i = 0; i < num_card_keywords && !is_isp_device; i++) { + if (strstr((char *)cap->card, isp_card_keywords[i]) != NULL) { + is_isp_device = 1; + } + } + + // 3. 可以添加更多过滤条件 + // 检查是否为UVC驱动(标准USB摄像头) + int is_uvc_device = (strstr((char *)cap->driver, "uvcvideo") != NULL); + + // 4. 检查总线信息(USB设备通常包含"usb") + int is_usb_device = (strstr((char *)cap->bus_info, "usb") != NULL); + + if (verbosity > 1) { + printf("V4L2_CORE: Device check - %s [%s]: standard_fmt=%d, is_isp=%d, is_uvc=%d, is_usb=%d\n", + device_path, cap->card, has_standard_format, is_isp_device, is_uvc_device, is_usb_device); + } + + // 5. 智能ISP设备处理:保留有用的ISP输出设备 + int is_useful_isp = 0; + if (is_isp_device) { + // 专门过滤有问题的设备:/dev/video12 (selfpath有底层问题) + if (strstr((char *)device_path, "/dev/video12") != NULL) { + if (verbosity > 1) { + printf("V4L2_CORE: Filtering problematic device: %s [%s] - hardware issue\n", device_path, cap->card); + } + return 0; // 明确过滤video12 + } + + // 检查是否为有用的ISP输出设备(只保留mainpath,不要selfpath) + if (strstr((char *)cap->card, "mainpath") != NULL) { + is_useful_isp = 1; + if (verbosity > 1) { + printf("V4L2_CORE: Keeping useful ISP device: %s [%s]\n", device_path, cap->card); + } + } + + // 过滤无用的ISP设备(统计、参数、selfpath等) + if (strstr((char *)cap->card, "statistics") != NULL || + strstr((char *)cap->card, "params") != NULL || + strstr((char *)cap->card, "selfpath") != NULL) { + if (verbosity > 1) { + printf("V4L2_CORE: Filtering useless ISP device: %s [%s]\n", device_path, cap->card); + } + return 0; // 明确过滤 + } + } + + // 返回判断结果:有标准格式且(不是ISP设备 或 是有用的ISP设备) + return (has_standard_format && (!is_isp_device || is_useful_isp)); +} + /* * enumerate available v4l2 devices * and creates list in vd->list_devices @@ -226,6 +344,34 @@ int enum_v4l2_devices() getV4l2()->m_v4l2_close(fd); continue; /*next dir entry*/ } + + int num_card_keywords = 0; + const char **card_keywords = get_card_keywords(&num_card_keywords); + + int is_card_keyword_device = 0; + for (int i = 0; i < num_card_keywords; i++) { + if(card_keywords[i] != NULL && strstr((const char *)v4l2_cap.card, card_keywords[i]) != NULL) { + is_card_keyword_device = 1; + break; + } + } + if(is_card_keyword_device) { + getV4l2()->m_v4l2_close(fd); + continue; /*next dir entry*/ + } + + // when project_id equals UOS2025073011543 filter out ISP devices + const char* current_project_id = get_project_id(); + if (current_project_id != NULL && strcmp(current_project_id, UOS2025073011543) == 0) { + if (!is_standard_camera_device(fd, &v4l2_cap, v4l2_device)) { + if (verbosity > 0) { + fprintf(stderr, "V4L2_CORE: ignore device %s - not a standard camera (driver: %s, card: %s) [project_id: %s]\n", + v4l2_device, v4l2_cap.driver, v4l2_cap.card, current_project_id); + } + getV4l2()->m_v4l2_close(fd); + continue; /*next dir entry*/ + } + } #endif getV4l2()->m_v4l2_close(fd); @@ -294,6 +440,7 @@ int enum_v4l2_devices() getUdev()->m_udev_enumerate_unref(enumerate); my_device_list.num_devices = num_dev; + printf("V4L2_CORE: num_devices is %d\n", num_dev); return(E_OK); } @@ -335,16 +482,16 @@ void v4l2core_init_device_list() * none * * returns: - * videodevice index in device list [0 - num_devices[ or -1 on error + * videodevice index in device list [0 - num_devices] or -1 on error */ int v4l2core_get_device_index(const char *videodevice) { - if(my_device_list.num_devices > 0 && my_device_list.list_devices != NULL && videodevice != NULL) + if(my_device_list.num_devices > 0 && my_device_list.list_devices != NULL && videodevice != NULL) { int dev_index = 0; for(dev_index = 0; dev_index < my_device_list.num_devices; ++dev_index) { - fprintf(stderr,"v4l2core_get_device_index dev_index(%d) videodevice:%x device:%x\n", dev_index, (unsigned int)videodevice, (unsigned int)(my_device_list.list_devices[dev_index].device)); + fprintf(stderr,"V4L2CORE: v4l2core_get_device_index: dev_index(%d) videodevice(%s) device(%s)\n", dev_index, videodevice, my_device_list.list_devices[dev_index].device); if(strcmp(videodevice, my_device_list.list_devices[dev_index].device)==0) { my_device_list.list_devices[dev_index].current = 1; diff --git a/libcam/libcam_v4l2core/v4l2_formats.c b/libcam/libcam_v4l2core/v4l2_formats.c index 161c3ee57..9e705e90d 100644 --- a/libcam/libcam_v4l2core/v4l2_formats.c +++ b/libcam/libcam_v4l2core/v4l2_formats.c @@ -119,6 +119,12 @@ static uint32_t decoder_supported_formats[] = 0 }; +// 是否开启8K预览 +static int enable_8k_preview = 0; +// 首选分辨率 +static int preferred_resolution_width = 0; +static int preferred_resolution_height = 0; + /* FIXME: doesn't support bigendian formats=> fourcc | (1 << 31) * get pixelformat from fourcc * args: @@ -678,27 +684,34 @@ int get_format_resolution_index(v4l2_dev_t *vd, int format, int width, int heigh return (-1); } + // 查找输入的分辨率是否在支持的分辨率列表中 + // 由于上游guvcview项目在解码3840x2160分辨率以上时会出现异常(出现条纹、崩溃等),所以我们需要限制可用分辨率到3840x2160 int i=0; for(i=0; i < vd->list_stream_formats[format].numb_res; i++) { - if( width == vd->list_stream_formats[format].list_stream_cap[i].width && - height == vd->list_stream_formats[format].list_stream_cap[i].height) - return (i); + int w = vd->list_stream_formats[format].list_stream_cap[i].width; + int h = vd->list_stream_formats[format].list_stream_cap[i].height; + if (width == w && height == h && is_valid_resolution(w, h)) { + return i; + } + } + + // 查找可以支持的最大分辨率 + // 由于上游guvcview项目在解码3840x2160分辨率以上时会出现异常(出现条纹、崩溃等),所以我们需要限制可用分辨率到3840x2160 + int tempwidth = 0; + int tempheight = 0; + int index = -1; + for(i=0; i < vd->list_stream_formats[format].numb_res; i++) + { + int w = vd->list_stream_formats[format].list_stream_cap[i].width; + int h = vd->list_stream_formats[format].list_stream_cap[i].height; + if (tempwidth <= w && tempheight <= h && is_valid_resolution(w, h)) { + tempwidth = w; + tempheight = h; + index = i; + } } - int tempwidth = 0; - int tempheight = 0; - int index = -1; - for(i=0; i < vd->list_stream_formats[format].numb_res; i++) - { - if( tempwidth <= vd->list_stream_formats[format].list_stream_cap[i].width && - tempheight <= vd->list_stream_formats[format].list_stream_cap[i].height) - { - tempwidth = vd->list_stream_formats[format].list_stream_cap[i].width; - tempheight = vd->list_stream_formats[format].list_stream_cap[i].height; - index = i; - } - } - return (index); + return index; } /* @@ -738,3 +751,100 @@ void free_frame_formats(v4l2_dev_t *vd) free(vd->list_stream_formats); vd->list_stream_formats = NULL; } + +int is_valid_resolution(int width, int height) +{ + // 希沃的8K分辨率是8192x2772,过多限制会导致此分辨率不可用 + if (enable_8k_preview) { + return (width > 0 && height > 0) + && (width % 16 == 0 && height % 4 == 0); + } + + // 由于上游guvcview项目在解码3840x2160分辨率以上时会出现异常(出现条纹、崩溃等),所以我们需要限制可用分辨率到3840x2160 + return (width > 0 && height > 0) + && (width <= MAX_WIDTH_LIMIT && height <= MAX_HEIGHT_LIMIT) + && (width % 16 == 0 && height % 8 == 0); +} + +/* + * set enable 8k preview + * args: + * enable - enable 8k preview + * + * asserts: + * none + * + * returns: void + */ +void set_enable_8k_preview(int enable) +{ + enable_8k_preview = enable; +} + +/* + * check if 8k preview is enabled + * args: + * none + * + * asserts: + * none + * + * returns: TRUE(1) if enabled + * FALSE(0) if not + */ +int is_enable_8k_preview() +{ + return enable_8k_preview; +} + +/** + * set preferred resolution + * args: + * width - preferred width + * height - preferred height + * + * asserts: + * none + * + * returns: void + */ +void set_preferred_resolution(int width, int height) +{ + if (width <= 0 || height <= 0) { + printf("V4L2_CORE: Invalid preferred resolution: %dx%d\n", width, height); + return; + } + + preferred_resolution_width = width; + preferred_resolution_height = height; +} + +/** + * get preferred resolution width + * args: + * none + * + * asserts: + * none + * + * returns: preferred width + */ +int get_preferred_resolution_width() +{ + return preferred_resolution_width; +} + +/** + * get preferred resolution height + * args: + * none + * + * asserts: + * none + * + * returns: preferred height + */ +int get_preferred_resolution_height() +{ + return preferred_resolution_height; +} diff --git a/libcam/libcam_v4l2core/v4l2_formats.h b/libcam/libcam_v4l2core/v4l2_formats.h index 61c1e7f35..0ce1a362e 100644 --- a/libcam/libcam_v4l2core/v4l2_formats.h +++ b/libcam/libcam_v4l2core/v4l2_formats.h @@ -24,6 +24,9 @@ #include "gviewv4l2core.h" #include "v4l2_core.h" +#define MAX_WIDTH_LIMIT 3840 +#define MAX_HEIGHT_LIMIT 2160 + /* * check pixelformat against decoder support formats * args: @@ -93,4 +96,80 @@ int get_format_resolution_index(v4l2_dev_t *vd, int format, int width, int heigh */ void free_frame_formats(v4l2_dev_t *vd); +/* + * check if requested resolution is valid + * args: + * width - requested width + * height - requested height + * + * asserts: + * none + * + * returns: TRUE(1) if valid + * FALSE(0) if not + */ +int is_valid_resolution(int width, int height); + +/* + * set enable 8k preview + * args: + * enable - enable 8k preview + * + * asserts: + * none + * + * returns: void + */ +void set_enable_8k_preview(int enable); + +/* + * check if 8k preview is enabled + * args: + * none + * + * asserts: + * none + * + * returns: TRUE(1) if enabled + * FALSE(0) if not + */ +int is_enable_8k_preview(); + +/** + * set preferred resolution + * args: + * width - preferred width + * height - preferred height + * + * asserts: + * none + * + * returns: void + */ +void set_preferred_resolution(int width, int height); + +/** + * get preferred resolution width + * args: + * none + * + * asserts: + * none + * + * returns: preferred width + */ +int get_preferred_resolution_width(); + +/** + * get preferred resolution height + * args: + * none + * + * asserts: + * none + * + * returns: preferred height + */ +int get_preferred_resolution_height(); + #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3b66cde2..158114e2b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,8 @@ file (GLOB_RECURSE RESOURCES ${PROJECT_SOURCE_DIR}/assets/assets.qrc) pkg_check_modules(3rd_lib REQUIRED dtkwidget dtkcore gobject-2.0 pciaccess + libva + libva-x11 # libv4l2 # libudev # libusb-1.0 diff --git a/src/assets/assets.qrc b/src/assets/assets.qrc index 77242fc92..ca85a2a67 100644 --- a/src/assets/assets.qrc +++ b/src/assets/assets.qrc @@ -7,6 +7,7 @@ images/icons/warning.svg deepin-camera.svg images/icons/light/Not connected.svg + images/icons/light/camera_switch_not_on.svg images/icons/light/photograph.svg images/icons/light/record video.svg images/icons/light/select-hover.svg @@ -18,6 +19,7 @@ images/icons/light/Stop Recording.svg images/icons/light/Timer Status.svg images/icons/dark/Not connected_dark.svg + images/icons/dark/camera_switch_not_on_dark.svg images/icons/dark/Take up_dark.svg images/icons/dark/button/photograph_dark.svg images/icons/dark/button/record video_dark.svg diff --git a/src/assets/images/icons/dark/camera_switch_not_on_dark.svg b/src/assets/images/icons/dark/camera_switch_not_on_dark.svg new file mode 100644 index 000000000..724e054a1 --- /dev/null +++ b/src/assets/images/icons/dark/camera_switch_not_on_dark.svg @@ -0,0 +1,57 @@ + + + 插图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/icons/light/camera_switch_not_on.svg b/src/assets/images/icons/light/camera_switch_not_on.svg new file mode 100644 index 000000000..724e054a1 --- /dev/null +++ b/src/assets/images/icons/light/camera_switch_not_on.svg @@ -0,0 +1,57 @@ + + + 插图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/org.deepin.camera.encode.json b/src/assets/org.deepin.camera.encode.json index 64bad69a5..da1ab4352 100644 --- a/src/assets/org.deepin.camera.encode.json +++ b/src/assets/org.deepin.camera.encode.json @@ -1,16 +1,146 @@ { - "magic": "dsg.config.meta", - "version": "1.0", - "contents": { - "mp4EncodeMode": { - "value": -1, - "serial": 0, - "flags": ["global"], - "name": "pugx mp4 code mode", - "name[zh_CN]": "特殊编码模式", - "description": "pugx mp4 code mode", - "permissions": "readwrite", - "visibility": "private" - } - } -} \ No newline at end of file + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "mp4EncodeMode": { + "value": -1, + "serial": 0, + "flags": ["global"], + "name": "pugx mp4 code mode", + "name[zh_CN]": "特殊编码模式", + "description": "pugx mp4 code mode", + "permissions": "readwrite", + "visibility": "private" + }, + "forceGles": { + "value": -1, + "serial": 0, + "flags": ["global"], + "name": "forc elgs show in wayland", + "name[zh_CN]": "在 wayland 下特殊显示模式", + "description": "special mode do use gles to show", + "permissions": "readwrite", + "visibility": "private" + }, + "projectID": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "project ID", + "name[zh_CN]": "不同项目下的定制内容", + "description": "Customized content under different projects", + "permissions": "readwrite", + "visibility": "private" + }, + "libVaDriverName": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "libva driver name", + "name[zh_CN]": "启用VAAPI硬件编解码的驱动名称,配置环境变量LIBVA_DRIVER_NAME,如LIBVA_DRIVER_NAME=zx", + "description": "Name of the VAAPI driver for enabling hardware codec", + "permissions": "readwrite", + "visibility": "private" + }, + "libVdpauDriver": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "vdpau driver", + "name[zh_CN]": "启用VDPAU硬件编解码的驱动名称,配置环境变量VDPAU_DRIVER,如VDPAU_DRIVER=nvidia_vdpau", + "description": "Name of the VDPAU driver for enabling hardware codec", + "permissions": "readwrite", + "visibility": "private" + }, + "supportCameraSwitch": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "is support camera switch or not", + "name[zh_CN]": "是否带有摄像头开关", + "description": "is support camera switch or not", + "permissions": "readwrite", + "visibility": "private" + }, + "previewNoDelay": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "is preview no delay or not", + "name[zh_CN]": "是否预览无延迟", + "description": "is preview no delay or not", + "permissions": "readwrite", + "visibility": "private" + }, + "enableUsbGroup": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "enable USB group", + "name[zh_CN]": "是否启用USB摄像头分组", + "description": "Enable USB group", + "permissions": "readwrite", + "visibility": "private" + }, + "enable8kPreview": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "enable 8k preview", + "name[zh_CN]": "是否启用8K预览", + "description": "Enable 8k preview", + "permissions": "readwrite", + "visibility": "private" + }, + "deviceBlacklist": { + "value": [], + "serial": 0, + "flags": ["global"], + "name": "device blacklist", + "name[zh_CN]": "设备黑名单", + "description": "Set device blacklist, format: vid,pid,name", + "permissions": "readwrite", + "visibility": "private" + }, + "CardKeywords": { + "value": ["IR Cam", "IR Camera", "Infrared", "IR_", "-IR", "ir_cam"], + "serial": 0, + "flags": ["global"], + "name": "camera card keywords", + "name[zh_CN]": "摄像头卡片名称关键字", + "description": "Set camera card name keywords", + "permissions": "readwrite", + "visibility": "private" + }, + "preferredResolution": { + "value": "", + "serial": 0, + "flags": ["global"], + "name": "preferred resolution", + "name[zh_CN]": "首选分辨率", + "description": "Set preferred resolution", + "permissions": "readwrite", + "visibility": "private" + }, + "isLowPerformanceDevice": { + "value": false, + "serial": 0, + "flags": ["global"], + "name": "is low performance device", + "name[zh_CN]": "是否是低性能设备", + "description": "Is low performance device", + "permissions": "readwrite", + "visibility": "private" + }, + "useRgbData": { + "value": -1, + "serial": 0, + "flags": ["global"], + "name": "use RGB data for preview", + "name[zh_CN]": "是否使用RGB数据进行预览显示,-1表示不设置,由系统自动判断;0表示强制关闭;1表示强制开启", + "description": "Force use RGB data for preview rendering. -1: not set (auto), 0: force disable, 1: force enable. When enabled, YUV frames will be converted to RGB format before display.", + "permissions": "readwrite", + "visibility": "private" + } + } +} diff --git a/src/main.cpp b/src/main.cpp index 5fc4984f9..b1f5f18d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,12 +5,15 @@ extern "C" { #include "camview.h" +#include "v4l2_formats.h" } #include "mainwindow.h" #include "capplication.h" #include "cameraconfig.h" #include "acobjectlist.h" #include "dbus_adpator.h" +#include "globalutils.h" +#include #ifdef DTKCORE_CLASS_DConfigFile #include @@ -27,10 +30,13 @@ extern "C" { #include #include +#include +#include #include #include #include +#include #ifndef DEEPIN_CAMERA_PRO #include "config.h" @@ -93,8 +99,24 @@ static bool CheckFFmpegEnv() return false; } +// 由于DDE在某些情况下会kill掉deepin-camera进程,同时埋点会认为deepin-camera出错,所以这里监听信号,正常退出 +// 添加信号处理函数 +static void handleSignal(int sig) +{ + qWarning() << "Received signal:" << sig; + // 在HW机器、SW机器上,发现使用exit(0)退出会导致Qt某些函数空指针异常,从而导致崩溃,疑似在资源回收环节出了问题。所以我们使用QApplication::quit()更优雅地退出。 + QApplication::quit(); + //exit(0); +} + int main(int argc, char *argv[]) { + // 将日志配置提前,以确保所有日志没有遗漏 + //qputenv("QT_LOGGING_RULES", "*.debug=true;*.info=true"); + DLogManager::registerConsoleAppender(); + DLogManager::registerFileAppender(); + qInfo() << "LogFile:" << DLogManager::getlogFilePath(); + // Task 326583 不参与合成器崩溃重连 unsetenv("QT_WAYLAND_RECONNECT"); @@ -108,6 +130,7 @@ int main(int argc, char *argv[]) } } DataManager::instance()->setEncodeEnv(bFFmpegEnv ? FFmpeg_Env : GStreamer_Env); + qWarning() << "EncodeEnv:" << (bFFmpegEnv ? "FFmpeg_Env" : "GStreamer_Env"); //root login for this application if (!QString(qgetenv("XDG_CURRENT_DESKTOP")).toLower().startsWith("deepin")) { @@ -121,44 +144,150 @@ int main(int argc, char *argv[]) format.setRenderableType(QSurfaceFormat::OpenGLES); format.setDefaultFormat(format); set_wayland_status(1); - - int mp4Encode = -1; + } + int mp4Encode = -1; #ifdef DTKCORE_CLASS_DConfigFile - //需要查询是否对PGUX设置MP4编码缓存特殊处理 - DConfig *dconfig = DConfig::create("org.deepin.camera", "org.deepin.camera.encode"); - if (dconfig && dconfig->isValid() && dconfig->keyList().contains("mp4EncodeMode")) { - mp4Encode = dconfig->value("mp4EncodeMode").toInt(); - set_pugx_status(mp4Encode); + //需要查询是否对X机型设置MP4编码缓存特殊处理 + DConfig *dconfig = DConfig::create("org.deepin.camera", "org.deepin.camera.encode"); + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("mp4EncodeMode")) { + mp4Encode = dconfig->value("mp4EncodeMode").toInt(); + set_pugx_status(mp4Encode); + } + + int forceGles = -1; + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("forceGles")) { + forceGles = dconfig->value("forceGles").toInt(); + set_forceGles(forceGles); + } + + QString projectId = ""; + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("projectID")) { + projectId = dconfig->value("projectID").toString(); + set_project_id(projectId.toUtf8().constData()); + } + + QString libVaDriverName = ""; + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("libVaDriverName")) { + libVaDriverName = dconfig->value("libVaDriverName").toString(); + set_libva_driver_name(libVaDriverName.toUtf8().constData()); + } + + QString libVdpauDriverName = ""; + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("libVdpauDriver")) { + libVdpauDriverName = dconfig->value("libVdpauDriver").toString(); + set_libvdpau_driver_name(libVdpauDriverName.toUtf8().constData()); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("supportCameraSwitch")) { + DataManager::instance()->setIsSupportCameraSwitch(dconfig->value("supportCameraSwitch").toBool()); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("previewNoDelay")) { + DataManager::instance()->setPreviewNoDelay(dconfig->value("previewNoDelay").toBool()); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("enableUsbGroup")) { + bool enable = dconfig->value("enableUsbGroup").toBool(); + qInfo() << "enable USB group:" << enable; + DataManager::instance()->setEnableUsbGroup(enable); + } + + // 是否开启8K预览通过DConfig控制,目前要求开启8K预览的厂家是希沃 + // 希沃的8K分辨率是8192x2772,希沃在8K预览的时候表现正常,在录像的时候表现异常,所以在开启8K预览的时候我们会禁用录像功能 + // 8K预览功能默认关闭,因为多个厂家的摄像头在8K预览的时候表现异常 + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("enable8kPreview")) { + bool enable = dconfig->value("enable8kPreview").toBool(); + qInfo() << "enable 8K preview:" << enable; + DataManager::instance()->setEnable8kPreview(enable); + set_enable_8k_preview(enable ? 1 : 0); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("deviceBlacklist")) { + QStringList deviceBlacklist = dconfig->value("deviceBlacklist").toStringList(); + qInfo() << "device blacklist:" << deviceBlacklist; + DataManager::instance()->setDeviceBlacklist(deviceBlacklist); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("CardKeywords")) { + QStringList cardKeywords = GlobalUtils::parseStringListConfig(dconfig->value("CardKeywords")); + QVector keywordBytes; + keywordBytes.reserve(cardKeywords.size()); + for (const QString &keyword : cardKeywords) { + keywordBytes.append(keyword.toUtf8()); } -#endif - qInfo() << "mp4EncodeMode value is:" << get_pugx_status(); - if (mp4Encode == -1) { - //判断是否是pgux - QStringList options; - options << QString(QStringLiteral("-c")); - options << QString(QStringLiteral("dmidecode -s system-product-name|awk '{print $NF}'")); - QProcess process; - process.start(QString(QStringLiteral("bash")), options); - process.waitForFinished(); - process.waitForReadyRead(); - QByteArray tempArray = process.readAllStandardOutput(); - char *charTemp = tempArray.data(); - QString str_output = QString(QLatin1String(charTemp)); - process.close(); - - if (str_output.contains("PGUX", Qt::CaseInsensitive)) { - mp4Encode = 1; - qDebug() << "this is PGUX"; - } else { - mp4Encode = 0; + + QVector keywordData; + keywordData.reserve(keywordBytes.size()); + for (const QByteArray &keyword : keywordBytes) { + keywordData.append(keyword.constData()); + } + qInfo() << "card keywords:" << cardKeywords; + set_card_keywords(keywordData.data(), keywordData.size()); + } + + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("preferredResolution")) { + QString preferredResolution = dconfig->value("preferredResolution").toString(); + qInfo() << "preferred resolution:" << preferredResolution; + if (!preferredResolution.isEmpty()) { + DataManager::instance()->setPreferredResolution(preferredResolution); + QSize size = DataManager::instance()->getPreferredResolution(); + if (!size.isNull()) { + set_preferred_resolution(size.width(), size.height()); } - qInfo() << "process find mp4EncodeMode value is:" << get_pugx_status(); } + } - set_pugx_status(mp4Encode); - qInfo() << "last mp4EncodeMode value is:" << get_pugx_status(); + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("useRgbData")) { + int useRgbData = dconfig->value("useRgbData").toInt(); + if (useRgbData != -1 && useRgbData != 0 && useRgbData != 1) { + qWarning() << "Invalid useRgbData value in config:" << useRgbData << "- mapping to default value -1 (auto)"; + useRgbData = -1; + } + qInfo() << "use RGB data for preview:" << useRgbData; + DataManager::instance()->setUseRgbData(useRgbData); } + if (!libVaDriverName.isEmpty()) { + qputenv("LIBVA_DRIVER_NAME", libVaDriverName.toLocal8Bit()); + } + + if (!libVdpauDriverName.isEmpty()) { + qputenv("VDPAU_DRIVER", libVdpauDriverName.toLocal8Bit()); + } +#endif + qInfo() << "mp4EncodeMode value is:" << get_pugx_status(); + qInfo() << "projectID value is:" << get_project_id(); + qInfo() << "libVaDriverName value is:" << get_libva_driver_name(); + qInfo() << "libVdpauDriverName value is:" << get_libvdpau_driver_name(); + qInfo() << "LIBVA_DRIVER_NAME value is:" << qgetenv("LIBVA_DRIVER_NAME"); + qInfo() << "VDPAU_DRIVER value is:" << qgetenv("VDPAU_DRIVER"); + if (mp4Encode == -1) { + //判断是否是X机型 + QStringList options; + options << QString(QStringLiteral("-c")); + options << QString(QStringLiteral("dmidecode -t 11 | grep 'String 4' | awk '{print $NF}' && " + "dmidecode -s system-product-name|awk '{print $NF}'")); + QProcess process; + process.start(QString(QStringLiteral("bash")), options); + process.waitForFinished(); + process.waitForReadyRead(); + QByteArray tempArray = process.readAllStandardOutput(); + char *charTemp = tempArray.data(); + QString str_output = QString(QLatin1String(charTemp)); + process.close(); + + if (str_output.contains("PGUX", Qt::CaseInsensitive)) { + mp4Encode = 1; + qDebug() << "this is PGUX"; + } else { + mp4Encode = 0; + } + qInfo() << "process find mp4EncodeMode value is:" << get_pugx_status(); + } + + set_pugx_status(mp4Encode); + qInfo() << "last mp4EncodeMode value is:" << get_pugx_status(); + QTime time; time.start(); QString lutDir = LUT_DIR; @@ -166,6 +295,9 @@ int main(int argc, char *argv[]) qDebug() << QString("initFilters cost %1 ms").arg(time.elapsed()); CApplication a(argc, argv); + // 注册信号处理函数 + // 放在此处,是为了确保Application已经创建了实例 + signal(SIGTERM, handleSignal); // kill默认发送的信号 //gst_init(&argc, &argv); qApp->setObjectName("deepin-camera"); @@ -185,12 +317,9 @@ int main(int argc, char *argv[]) qApp->setApplicationDisplayName(QObject::tr("Camera")); //设置产品名称 qApp->setProductName(QObject::tr("Camera")); - //日志 - DLogManager::registerConsoleAppender(); - DLogManager::registerFileAppender(); - qInfo() << "LogFile:" << DLogManager::getlogFilePath(); //版本 qApp->setApplicationVersion(DApplication::buildVersion(VERSION)); + qWarning() << "Version:" << DApplication::buildVersion(VERSION); QIcon myIcon = QIcon::fromTheme("deepin-camera"); qApp->setWindowIcon(myIcon); qApp->setProductIcon(myIcon);//08月21获悉已添加到系统,故更改为从系统获取 diff --git a/src/src/LPF_V4L2.c b/src/src/LPF_V4L2.c index a91fd4193..a6a180b41 100644 --- a/src/src/LPF_V4L2.c +++ b/src/src/LPF_V4L2.c @@ -1,5 +1,5 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// Copyright (C) 2020 - 2026 Uniontech Software Technology Co.,Ltd. +// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -18,6 +18,7 @@ extern "C" { #include "audio.h" #include "core_io.h" #include "colorspaces.h" +#include "v4l2_formats.h" int camInit(const char *devicename) { @@ -49,11 +50,12 @@ int camInit(const char *devicename) free(device_name); /*加载配置文件*/ config_load(config_file); - /*用option更新配置文件*/ - config_update(my_options); /*获取配置项数据*/ config_t *my_config = config_get(); - debug_level = my_options->verbosity; + debug_level = my_config->verbosity; + my_options->verbosity = my_config->verbosity; + /*用option更新配置文件*/ + config_update(my_options); int audio = AUDIO_PORTAUDIO; @@ -209,7 +211,18 @@ int camInit(const char *devicename) v4l2core_prepare_new_format(my_vd, (int)my_config->format); if ((strcmp(my_config->device_name, devlist->list_devices[my_vd->this_device].name) == 0) && (strcmp(my_config->device_location, my_vd->videodevice) == 0)) { - v4l2core_prepare_new_resolution(my_vd, my_config->width, my_config->height); + // DConfig preferredResolution 优先于本地配置文件中的分辨率 + if (get_preferred_resolution_width() > 0 && get_preferred_resolution_height() > 0) { + v4l2core_prepare_new_resolution(my_vd, + get_preferred_resolution_width(), + get_preferred_resolution_height()); + my_config->width = get_my_width(); + my_config->height = get_my_height(); + } else { + v4l2core_prepare_new_resolution(my_vd, my_config->width, my_config->height); + my_config->width = get_my_width(); + my_config->height = get_my_height(); + } ret = v4l2core_update_old_format(my_vd, my_config->width, my_config->height, v4l2core_get_requested_frame_format(my_vd)); } else { v4l2core_prepare_valid_resolution(my_vd); diff --git a/src/src/Settings.cpp b/src/src/Settings.cpp index cd6d58edc..4009d21a0 100644 --- a/src/src/Settings.cpp +++ b/src/src/Settings.cpp @@ -1,10 +1,14 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later +extern "C" { +#include "v4l2_formats.h" +} #include "Settings.h" #include "camview.h" +#include "v4l2_formats.h" #include "gviewv4l2core.h" #include "capplication.h" #include "camera.h" @@ -112,15 +116,11 @@ void Settings::setNewResolutionList() if (format_index >= 0 && resolu_index >= 0) { for (int i = 0 ; i < list_stream_formats[format_index].numb_res; i++) { - if ((list_stream_formats[format_index].list_stream_cap[i].width > 0 - && list_stream_formats[format_index].list_stream_cap[i].height > 0) && - (list_stream_formats[format_index].list_stream_cap[i].width < 7680 - && list_stream_formats[format_index].list_stream_cap[i].height < 4320) && - ((list_stream_formats[format_index].list_stream_cap[i].width % 8) == 0 - && (list_stream_formats[format_index].list_stream_cap[i].width % 16) == 0 - && (list_stream_formats[format_index].list_stream_cap[i].height % 8) == 0)) { + int w = list_stream_formats[format_index].list_stream_cap[i].width; + int h = list_stream_formats[format_index].list_stream_cap[i].height; + if (is_valid_resolution(w, h)) { //加入分辨率的字符串 - QString res_str = QString("%1x%2").arg(list_stream_formats[format_index].list_stream_cap[i].width).arg(list_stream_formats[format_index].list_stream_cap[i].height); + QString res_str = QString("%1x%2").arg(w).arg(h); //去重 if (resolutionDatabase.contains(res_str) == false) resolutionDatabase.append(res_str); @@ -136,8 +136,8 @@ void Settings::setNewResolutionList() QStringList resolutiontemp1 = resolutionDatabase[j].split("x"); QStringList resolutiontemp2 = resolutionDatabase[j + 1].split("x"); - if ((resolutiontemp1[0].toInt() <= resolutiontemp2[0].toInt()) - || (resolutiontemp1[1].toInt() < resolutiontemp2[1].toInt())) { + if ((resolutiontemp1[0].toInt() < resolutiontemp2[0].toInt()) || + (resolutiontemp1[0].toInt() == resolutiontemp2[0].toInt() && (resolutiontemp1[1].toInt() < resolutiontemp2[1].toInt()))) { QString resolutionstr = resolutionDatabase[j + 1]; resolutionDatabase[j + 1] = resolutionDatabase[j]; resolutionDatabase[j] = resolutionstr; @@ -155,17 +155,23 @@ void Settings::setNewResolutionList() for (int i = 0; i < resolutionDatabase.size(); i++) { QStringList resolutiontemp = resolutionDatabase[i].split("x"); - if ((v4l2core_get_frame_width(get_v4l2_device_handler()) == resolutiontemp[0].toInt()) && - (v4l2core_get_frame_height(get_v4l2_device_handler()) == resolutiontemp[1].toInt())) { + if (resolutiontemp.size() >= 2 && + v4l2core_get_frame_width(get_v4l2_device_handler()) == resolutiontemp[0].toInt() && + v4l2core_get_frame_height(get_v4l2_device_handler()) == resolutiontemp[1].toInt()) { defres = i; //set selected resolution index break; } } - + // 抑制中间态的 resolutionchanged, 仅在最后发射一次正确值 + m_updatingResolution = true; resolutionmodeFamily->setData("items", resolutionDatabase); - - //设置当前分辨率的索引 m_settings->setOption(QString("outsetting.resolutionsetting.resolution"), defres); + QTimer::singleShot(0, this, [this, resolutionDatabase, defres]() { + m_updatingResolution = false; + if (defres >= 0 && defres < resolutionDatabase.size()) { + emit resolutionchanged(resolutionDatabase[defres]); + } + }); } else { resolutionDatabase.clear(); resolutionDatabase.append(QString(tr("None"))); @@ -222,6 +228,8 @@ QVariant Settings::getBackOption(const QString &opt) void Settings::onValueChanged(const QString & key, const QVariant & value) { if (key.startsWith("outsetting.resolutionsetting.resolution")) { + if (m_updatingResolution) + return; auto mode_opt = m_settings->option("outsetting.resolutionsetting.resolution"); if (value >= 0 && mode_opt->data("items").toStringList().size() > value.toInt()) { QString mode = mode_opt->data("items").toStringList()[value.toInt()]; diff --git a/src/src/Settings.h b/src/src/Settings.h index 584e09c74..77c0472f2 100644 --- a/src/src/Settings.h +++ b/src/src/Settings.h @@ -1,4 +1,4 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -160,6 +160,7 @@ public slots: QString m_configPath; QPointer m_settings; QSettingBackend* m_backend; + bool m_updatingResolution = false; }; } diff --git a/src/src/basepub/datamanager.cpp b/src/src/basepub/datamanager.cpp index 4fbbd3c65..8c2907188 100644 --- a/src/src/basepub/datamanager.cpp +++ b/src/src/basepub/datamanager.cpp @@ -3,6 +3,10 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +extern "C" { +#include "v4l2_formats.h" +} + #include "datamanager.h" #include "../capplication.h" @@ -55,6 +59,96 @@ bool DataManager::encExists(){ return m_H264EncoderExists; } +void DataManager::setDeviceBlacklist(const QStringList &blacklist) +{ + // 无论是否为空,都先清空现有黑名单 + m_deviceBlacklistSet.clear(); + if (blacklist.isEmpty()) { + qInfo() << "Empty blacklist provided"; + return; + } + + // 每一条黑名单项的格式都是 vid,pid,name + // 其中vid和pid都是4位十六进制数,name是设备名称 + // 后续匹配操作的时候,我们会忽略大小写 + for (const QString &item : blacklist) { + QStringList parts = item.split(","); + if (parts.size() != 3) { + qWarning() << "Drop blacklist item(format error):" << item; + continue; + } + + // 验证VID和PID是否为4位十六进制数 + static const QRegularExpression hexPattern("^[0-9a-fA-F]{4}$"); + if (!hexPattern.match(parts[0]).hasMatch() || !hexPattern.match(parts[1]).hasMatch()) { + qWarning() << "Drop blacklist item(invalid VID/PID):" << item; + continue; + } + + // 验证设备名称不为空,且长度不超过100个字符 + if (parts[2].trimmed().isEmpty() || parts[2].size() > 100) { + qWarning() << "Drop blacklist item(empty device name OR too long):" << item; + continue; + } + + qInfo() << "Add blacklist item:" << item; + m_deviceBlacklistSet.insert(item.toLower()); + } +}; + +bool DataManager::isDeviceValid(const QString &vid, const QString &pid, const QString &name) +{ + // 参数验证 + if (vid.isEmpty() || pid.isEmpty() || name.isEmpty()) { + return true; // 空参数视为有效,避免误判 + } + + QString key = vid.toLower() + "," + pid.toLower() + "," + name.toLower(); + return !m_deviceBlacklistSet.contains(key); +} + +void DataManager::setPreferredResolution(const QString &resolution) +{ + if (resolution.isEmpty()) { + qInfo() << "Preferred resolution is empty"; + return; + } + + QStringList parts = resolution.split("x"); + if (parts.size() != 2) { + qWarning() << "Invalid resolution format:" << resolution << "Expected format: WIDTHxHEIGHT"; + return; + } + + bool widthOk, heightOk; + int width = parts[0].toInt(&widthOk); + int height = parts[1].toInt(&heightOk); + + if (!widthOk || !heightOk || width <= 0 || height <= 0) { + qWarning() << "Invalid resolution values:" << resolution; + return; + } + + // 添加合理的上限检查,例如16K + static const int MAX_RESOLUTION = 16384; + if (width > MAX_RESOLUTION || height > MAX_RESOLUTION) { + qWarning() << "Resolution exceeds maximum limit:" << resolution; + return; + } + + if (!is_valid_resolution(width, height)) { + qWarning() << "Resolution does not meet validation requirements:" << resolution; + return; + } + + m_preferredResolution = QSize(width, height); +} + +QSize DataManager::getPreferredResolution() +{ + return m_preferredResolution; +} + DataManager *DataManager::instance() { if (m_dataManager == nullptr) { @@ -73,5 +167,6 @@ DataManager::DataManager() m_encodeEnv = GStreamer_Env; m_devStatus = DeviceStatus::NOCAM; m_H264EncoderExists = false; + m_preferredResolution = QSize(0, 0); } diff --git a/src/src/basepub/datamanager.h b/src/src/basepub/datamanager.h index e2c6c16d3..26d012932 100644 --- a/src/src/basepub/datamanager.h +++ b/src/src/basepub/datamanager.h @@ -8,6 +8,7 @@ #include #include +#include enum GridType { @@ -143,6 +144,85 @@ class DataManager: public QObject void setEncExists(bool status); bool encExists(); + /** + * @brief 设置是否带有摄像头开关 + * @param isTrue + */ + void setIsSupportCameraSwitch(bool isTrue) { m_isSupportCameraSwitch = isTrue; }; + /** + * @brief 获取是否带有摄像头开关 + * @return + */ + bool isSupportCameraSwitch() { return m_isSupportCameraSwitch; }; + + /** + * @brief 设置是否预览无延迟 + * @param isTrue + */ + void setPreviewNoDelay(bool isTrue) { m_isPreviewNoDelay = isTrue; }; + /** + * @brief 获取是否预览无延迟 + * @return + */ + bool isPreviewNoDelay() { return m_isPreviewNoDelay; }; + + /** + * @brief 设置是否启用USB摄像头分组 + * @param enable + */ + void setEnableUsbGroup(bool enable) { m_enableUsbGroup = enable; }; + /** + * @brief 获取是否启用USB摄像头分组 + * @return + */ + bool isEnableUsbGroup() const { return m_enableUsbGroup; }; + + /** + * @brief 设置是否启用8K预览 + * @param enable + */ + void setEnable8kPreview(bool enable) { m_enable8kPreview = enable; }; + + /** + * @brief 获取是否启用8K预览 + * @return + */ + bool isEnable8kPreview() const { return m_enable8kPreview; }; + + /** + * @brief 设置Camera设备黑名单 + * @param blacklist + */ + void setDeviceBlacklist(const QStringList &blacklist); + /** + * @brief 检查当前设备是否有效 + * @return + */ + bool isDeviceValid(const QString &vid, const QString &pid, const QString &name); + + /** + * @brief 设置首选分辨率 + * @param resolution 分辨率字符串,格式如 "1920x1080" + */ + void setPreferredResolution(const QString &resolution); + + /** + * @brief 获取首选分辨率 + * @return 分辨率 + */ + QSize getPreferredResolution(); + + /** + * @brief 设置RGB数据预览模式 + * @param mode -1:不设置(自动), 0:强制关闭, 1:强制开启 + */ + void setUseRgbData(int mode) { m_useRgbData = mode; }; + + /** + * @brief 获取RGB数据预览模式 + * @return -1:不设置(自动), 0:强制关闭, 1:强制开启 + */ + int getUseRgbData() const { return m_useRgbData; }; private: DataManager(); static DataManager *m_dataManager; @@ -152,5 +232,12 @@ class DataManager: public QObject EncodeEnv m_encodeEnv; volatile enum DeviceStatus m_devStatus; bool m_H264EncoderExists; + bool m_isSupportCameraSwitch = false; // 是否带有摄像头开关 + bool m_isPreviewNoDelay = false; // 是否预览无延迟 + bool m_enableUsbGroup = false; // 是否启用USB摄像头分组 + bool m_enable8kPreview = false; // 是否启用8K预览 + QSet m_deviceBlacklistSet; // 设备黑名单 + QSize m_preferredResolution; // 首选分辨率 + int m_useRgbData = -1; // RGB数据预览模式:-1不设置(自动), 0强制关闭, 1强制开启 }; #endif // DATAMANAGER_H diff --git a/src/src/basepub/load_libs.c b/src/src/basepub/load_libs.c index 34559c9bd..fe0a1b276 100644 --- a/src/src/basepub/load_libs.c +++ b/src/src/basepub/load_libs.c @@ -121,6 +121,8 @@ static LoadLibs *newClass(void) PrintError(); pLibs->m_avcodec_receive_packet = (uos_avcodec_receive_packet)dlsym(handle, "avcodec_receive_packet"); PrintError(); + pLibs->m_avcodec_get_hw_config = (uos_avcodec_get_hw_config)dlsym(handle, "avcodec_get_hw_config"); + PrintError(); pLibs->m_av_packet_free = (uos_av_packet_free)dlsym(handle, "av_packet_free"); PrintError(); pLibs->m_avcodec_fill_audio_frame = (uos_avcodec_fill_audio_frame)dlsym(handle, "avcodec_fill_audio_frame"); @@ -342,6 +344,8 @@ static LoadAvutil *newAvutil(void) PrintError(); Avutil->m_av_get_media_type_string = (uos_av_get_media_type_string)dlsym(handle5, "av_get_media_type_string"); PrintError(); + Avutil->m_av_get_pix_fmt_name = (uos_av_get_pix_fmt_name)dlsym(handle5, "av_get_pix_fmt_name"); + PrintError(); Avutil->m_av_image_get_buffer_size = (uos_av_image_get_buffer_size)dlsym(handle5, "av_image_get_buffer_size"); PrintError(); //----vaapi---- @@ -436,6 +440,8 @@ static LoadUdev *newUdev(void) PrintError(); pUdev->m_udev_monitor_receive_device = (uos_udev_monitor_receive_device)dlsym(handle, "udev_monitor_receive_device"); PrintError(); + pUdev->m_udev_device_get_subsystem = (uos_udev_device_get_subsystem)dlsym(handle, "udev_device_get_subsystem"); + PrintError(); pUdev->m_udev_device_get_devtype = (uos_udev_device_get_devtype)dlsym(handle, "udev_device_get_devtype"); PrintError(); pUdev->m_udev_device_get_action = (uos_udev_device_get_action)dlsym(handle, "udev_device_get_action"); diff --git a/src/src/basepub/load_libs.h b/src/src/basepub/load_libs.h index e890a8415..3ce2e4bc9 100644 --- a/src/src/basepub/load_libs.h +++ b/src/src/basepub/load_libs.h @@ -45,6 +45,8 @@ typedef int (*uos_avcodec_send_packet)(AVCodecContext *avctx, const AVPacket *av typedef int (*uos_avcodec_receive_frame)(AVCodecContext *avctx, AVFrame *frame); //int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt); typedef int (*uos_avcodec_receive_packet)(AVCodecContext *avctx, AVPacket *avpkt); +//const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index); +typedef const AVCodecHWConfig *(*uos_avcodec_get_hw_config)(const AVCodec *codec, int index); //void av_packet_free(AVPacket **pkt); typedef void (*uos_av_packet_free)(AVPacket **pkt); //int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels,enum AVSampleFormat sample_fmt, const uint8_t *buf,int buf_size, int align); @@ -128,6 +130,7 @@ typedef struct _LoadLibs { uos_avcodec_send_packet m_avcodec_send_packet; uos_avcodec_receive_frame m_avcodec_receive_frame; uos_avcodec_receive_packet m_avcodec_receive_packet; + uos_avcodec_get_hw_config m_avcodec_get_hw_config; uos_av_packet_free m_av_packet_free; uos_avcodec_fill_audio_frame m_avcodec_fill_audio_frame; uos_av_packet_unref m_av_packet_unref; @@ -235,6 +238,8 @@ typedef int (*uos_av_samples_get_buffer_size)(int *linesize, int nb_channels, in enum AVSampleFormat sample_fmt, int align); //const char *av_get_media_type_string(enum AVMediaType media_type); typedef const char *(*uos_av_get_media_type_string)(enum AVMediaType media_type); +//const char *av_get_pix_fmt_name(enum AVPixelFormat pix_fmt); +typedef const char *(*uos_av_get_pix_fmt_name)(enum AVPixelFormat pix_fmt); //int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align); typedef int (*uos_av_image_get_buffer_size)(enum AVPixelFormat pix_fmt, int width, int height, int align); @@ -254,6 +259,7 @@ typedef struct _LoadAvutil { uos_av_free m_av_free; uos_av_samples_get_buffer_size m_av_samples_get_buffer_size; uos_av_get_media_type_string m_av_get_media_type_string;// + uos_av_get_pix_fmt_name m_av_get_pix_fmt_name; uos_av_image_get_buffer_size m_av_image_get_buffer_size; //----VAAPI------ uos_av_hwdevice_ctx_create m_av_hwdevice_ctx_create; diff --git a/src/src/camera.cpp b/src/src/camera.cpp index 5883e717e..0cfec5a47 100755 --- a/src/src/camera.cpp +++ b/src/src/camera.cpp @@ -135,7 +135,7 @@ void Camera::restartCamera() { if (!m_camera || m_camera->status() == QCamera::UnloadedStatus) { QList availableCams = QCameraInfo::availableCameras(); - if ((availableCams.isEmpty() || (m_camera && availableCams.indexOf(QCameraInfo(*m_camera)) != -1)) + if ((availableCams.isEmpty() || (m_camera && m_camera->error() == QCamera::NoError && availableCams.indexOf(QCameraInfo(*m_camera)) != -1)) && !m_cameraDevList.isEmpty()) { emit cameraCannotUsed(); return; @@ -242,10 +242,18 @@ void Camera::startCamera(const QString &devName) stopCamera(); m_camera = new QCamera(devName.toStdString().c_str()); - QCameraInfo cameraInfo(*m_camera); - m_curDevName = devName; connect(m_camera, SIGNAL(statusChanged(QCamera::Status)), this, SLOT(onCameraStatusChanged(QCamera::Status))); + connect(m_camera, SIGNAL(error(QCamera::Error)), this, SIGNAL(cameraCannotUsed())); + + if (m_camera->error() != QCamera::NoError) { + stopCamera(); + emit cameraCannotUsed(); + return; + } + + QCameraInfo cameraInfo(*m_camera); + m_curDevName = devName; m_imageCapture = new QCameraImageCapture(m_camera); m_mediaRecoder = new QMediaRecorder(m_camera); diff --git a/src/src/circlepushbutton.cpp b/src/src/circlepushbutton.cpp index cf1474891..3b5f72cc4 100644 --- a/src/src/circlepushbutton.cpp +++ b/src/src/circlepushbutton.cpp @@ -196,7 +196,7 @@ void circlePushButton::mousePressEvent(QMouseEvent *event) void circlePushButton::mouseMoveEvent(QMouseEvent *event) { - //解决bug 在按钮中可拖动相机界面,https://pms.uniontech.com/zentao/bug-view-100647.html + //解决bug 在按钮中可拖动相机界面,bug 100647 Q_UNUSED(event); return; } diff --git a/src/src/devnummonitor.cpp b/src/src/devnummonitor.cpp index a6757894c..4d50c5b5c 100644 --- a/src/src/devnummonitor.cpp +++ b/src/src/devnummonitor.cpp @@ -44,28 +44,37 @@ void DevNumMonitor::startCheck() void DevNumMonitor::timeOutSlot() { - check_device_list_events(get_v4l2_device_handler()); + // 检查设备列表变化事件 + if (check_device_list_events(get_v4l2_device_handler())) { + emit deviceListChanged(); + } if (get_device_list()->num_devices <= 1) { emit seltBtnStateDisable(); if (get_device_list()->num_devices < 1) { //没有设备发送信号 if (!m_noDevice) { + qWarning() << "No device found"; emit noDeviceFound(); m_noDevice = true; } qDebug() << "There is no camera connected!"; } else { + if (m_noDevice) { + v4l2_dev_sys_data_t *v4l2_devices = get_device_list()->list_devices; + if (v4l2_devices) { + qWarning() << "Device found:" << v4l2_devices[0].device << v4l2_devices[0].name; + } + } m_noDevice = false; emit existDevice(); - qDebug() << "There is a camera connected!"; + // qDebug() << "existDevice!"; } } else { m_noDevice = false; emit existDevice(); //显示切换按钮 emit seltBtnStateEnable(); - qDebug() << "There are two or more cameras connected!"; } } diff --git a/src/src/devnummonitor.h b/src/src/devnummonitor.h index 1176ffbc3..551c18949 100644 --- a/src/src/devnummonitor.h +++ b/src/src/devnummonitor.h @@ -60,6 +60,11 @@ class DevNumMonitor: public QObject */ void existDevice(); + /** + * @brief deviceListChanged 相机设备列表改变信号 + */ + void deviceListChanged(); + protected: /** * @brief run 运行 diff --git a/src/src/filterpreviewbutton.cpp b/src/src/filterpreviewbutton.cpp index 8dae1b30b..1583f84dc 100644 --- a/src/src/filterpreviewbutton.cpp +++ b/src/src/filterpreviewbutton.cpp @@ -16,7 +16,7 @@ extern "C" { DWIDGET_USE_NAMESPACE; -#define MARGIN 5 +#define MARGIN 6 #define IMAGE_SIZE 40 #define BUTTON_SIZE (IMAGE_SIZE + MARGIN * 2) @@ -188,7 +188,7 @@ void filterPreviewButton::mousePressEvent(QMouseEvent *event) void filterPreviewButton::mouseMoveEvent(QMouseEvent *event) { - //解决bug 在按钮中可拖动相机界面,https://pms.uniontech.com/zentao/bug-view-100647.html + //解决bug 在按钮中可拖动相机界面,bug 100647 Q_UNUSED(event); return; } diff --git a/src/src/globalutils.cpp b/src/src/globalutils.cpp index 24aa73906..d7702e300 100644 --- a/src/src/globalutils.cpp +++ b/src/src/globalutils.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -11,11 +11,17 @@ #include #include +#ifdef DTKCORE_CLASS_DConfigFile +#include +#include +#endif + // 相机配置文件 const QString CAMERA_CONF_PATH = "/usr/share/deepin-camera/camera.conf"; #define LOW_PERFORMANCE_BOARD "LowPerformanceBoard" QStringList GlobalUtils::m_LowPerformanceBoards = QStringList(); +bool GlobalUtils::m_IsLowPerformanceDevice = false; struct DMIInfo { QString biosManufacturer; @@ -56,6 +62,12 @@ const QDBusArgument &operator>>(const QDBusArgument &argument, DMIInfo &mystruct bool GlobalUtils::isLowPerformanceBoard() { +#ifdef DTKCORE_CLASS_DConfigFile + if (m_IsLowPerformanceDevice) { + return true; + } +#endif + qDBusRegisterMetaType(); QDBusInterface *monitorInterface = new QDBusInterface("com.deepin.system.SystemInfo", "/com/deepin/system/SystemInfo", "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); if (!monitorInterface->isValid()) @@ -86,4 +98,39 @@ void GlobalUtils::loadCameraConf() } else { m_LowPerformanceBoards = QStringList(); } + +#ifdef DTKCORE_CLASS_DConfigFile + m_IsLowPerformanceDevice = false; + std::unique_ptr dconfig( + DTK_CORE_NAMESPACE::DConfig::create("org.deepin.camera", "org.deepin.camera.encode")); + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("isLowPerformanceDevice")) { + m_IsLowPerformanceDevice = dconfig->value("isLowPerformanceDevice").toBool(); + } +#endif +} + +QStringList GlobalUtils::parseStringListConfig(const QVariant &value) +{ + QStringList rawList; + + if (value.type() == QVariant::StringList) { + rawList = value.toStringList(); + } else if (value.type() == QVariant::List) { + const QVariantList variantList = value.toList(); + for (const QVariant &item : variantList) { + if (item.type() == QVariant::String) { + rawList.append(item.toString()); + } + } + } else { + return QStringList(); + } + + QStringList result; + for (const QString &keyword : rawList) { + if (!keyword.trimmed().isEmpty()) { + result.append(keyword); + } + } + return result; } diff --git a/src/src/globalutils.h b/src/src/globalutils.h index 51e021d44..2572a1295 100644 --- a/src/src/globalutils.h +++ b/src/src/globalutils.h @@ -7,6 +7,8 @@ #define GLOBALUTILS_H #include +#include +#include class GlobalUtils { @@ -17,8 +19,11 @@ class GlobalUtils // 加载相机配置 static void loadCameraConf(); + static QStringList parseStringListConfig(const QVariant &value); + private: static QStringList m_LowPerformanceBoards; + static bool m_IsLowPerformanceDevice; }; #endif // GLOBALUTILS_H diff --git a/src/src/imageitem.cpp b/src/src/imageitem.cpp index a47da7f6b..4a0220dc2 100644 --- a/src/src/imageitem.cpp +++ b/src/src/imageitem.cpp @@ -229,7 +229,7 @@ void ImageItem::mousePressEvent(QMouseEvent *event) void ImageItem::mouseMoveEvent(QMouseEvent *event) { - //解决bug 在缩略图中可拖动相机界面,https://pms.uniontech.com/zentao/bug-view-100647.html + //解决bug 在缩略图中可拖动相机界面,bug 100647 Q_UNUSED(event); return; } diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp index 57cba7182..862e633d3 100644 --- a/src/src/mainwindow.cpp +++ b/src/src/mainwindow.cpp @@ -1,8 +1,11 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later +extern "C" { +#include "v4l2_formats.h" +} #include "mainwindow.h" #include "capplication.h" #include "v4l2_core.h" @@ -62,7 +65,7 @@ const int CMainWindow::minWindowHeight = 510; const int rightOffset = 10; const int SwitchcameraDiam = 40; //切换摄像头直径 -const int photeRecordDiam = 64; +const int photoRecordDiam = 64; const int snapLabelDiam = 52; const int switchBtnWidth = 50; const int switchBtnHeight = 26; @@ -765,6 +768,7 @@ CMainWindow::CMainWindow(QWidget *parent) m_SpaceKeyInterval = QDateTime::currentMSecsSinceEpoch(); this->setObjectName(MAIN_WINDOW); this->setAccessibleName(MAIN_WINDOW); + setTitlebarShadowEnabled(false); titlebar()->deleteLater(); setupTitlebar(); @@ -892,7 +896,7 @@ void CMainWindow::initBlockShutdown() if (m_reply.isValid()) { QDBusReply tmp = m_reply; m_reply = QDBusReply(); - qDebug() << "init Nublock shutdown."; + qDebug() << "init Unblock shutdown."; } } @@ -922,7 +926,7 @@ void CMainWindow::initBlockSleep() if (m_replySleep.isValid()) { QDBusReply tmp = m_replySleep; m_replySleep = QDBusReply(); - qDebug() << "init Nublock sleep."; + qDebug() << "init Unblock sleep."; } } @@ -1106,16 +1110,11 @@ void CMainWindow::settingDialog() //format_index = -1 &&resolu_index = -1 表示设备被占用或者不存在 for (int i = 0 ; i < list_stream_formats[format_index].numb_res; i++) { - - if ((list_stream_formats[format_index].list_stream_cap[i].width > 0 - && list_stream_formats[format_index].list_stream_cap[i].height > 0) && - (list_stream_formats[format_index].list_stream_cap[i].width < 7680 - && list_stream_formats[format_index].list_stream_cap[i].height < 4320) && - ((list_stream_formats[format_index].list_stream_cap[i].width % 8) == 0 - && (list_stream_formats[format_index].list_stream_cap[i].width % 16) == 0 - && (list_stream_formats[format_index].list_stream_cap[i].height % 8) == 0)) { + int w = list_stream_formats[format_index].list_stream_cap[i].width; + int h = list_stream_formats[format_index].list_stream_cap[i].height; + if (is_valid_resolution(w, h)) { //加入分辨率的字符串 - QString res_str = QString("%1x%2").arg(list_stream_formats[format_index].list_stream_cap[i].width).arg(list_stream_formats[format_index].list_stream_cap[i].height); + QString res_str = QString("%1x%2").arg(w).arg(h); //去重 if (resolutionDatabase.contains(res_str) == false) @@ -1149,15 +1148,21 @@ void CMainWindow::settingDialog() } - for (int i = 0; i < resolutionDatabase.size(); i++) { - QStringList resolutiontemp = resolutionDatabase[i].split("x"); + int savedRes = resolutionmodeFamily->value().toInt(); + if (savedRes >= 0 && savedRes < resolutionDatabase.size()) { + defres = savedRes; + } else { + for (int i = 0; i < resolutionDatabase.size(); i++) { + QStringList resolutiontemp = resolutionDatabase[i].split("x"); + + if (resolutiontemp.size() >= 2 && + v4l2core_get_frame_width(get_v4l2_device_handler()) == resolutiontemp[0].toInt() && + v4l2core_get_frame_height(get_v4l2_device_handler()) == resolutiontemp[1].toInt()) { + defres = i; //设置分辨率下拉菜单当前索引 + break; + } - if ((v4l2core_get_frame_width(get_v4l2_device_handler()) == resolutiontemp[0].toInt()) && - (v4l2core_get_frame_height(get_v4l2_device_handler()) == resolutiontemp[1].toInt())) { - defres = i; //设置分辨率下拉菜单当前索引 - break; } - } resolutionmodeFamily->setData("items", resolutionDatabase); @@ -1241,6 +1246,7 @@ void CMainWindow::loadAfterShow() if(DataManager::instance()->encodeEnv() != QCamera_Env) { connect(m_devnumMonitor, SIGNAL(existDevice()), m_videoPre, SLOT(onRestartDevices()));//重启设备 connect(m_devnumMonitor, SIGNAL(noDeviceFound()), m_videoPre, SLOT(onRestartDevices()));//重启设备 + connect(m_devnumMonitor, SIGNAL(deviceListChanged()), m_videoPre, SLOT(updateValidDevices())); // 更新可用设备列表 } else if (DataManager::instance()->encodeEnv() == QCamera_Env) { initCameraConnection(); } @@ -1254,11 +1260,16 @@ void CMainWindow::loadAfterShow() m_windowStateThread = new windowStateThread(m_bWayland, this); connect(m_windowStateThread, &windowStateThread::someWindowFullScreen, this, &CMainWindow::onStopPhotoAndRecord); + QTimer::singleShot(100, this, [=](){ + resize(this->size() + QSize(1, 1)); + resize(this->size() + QSize(-1, -1)); + }); + QJsonObject obj{ {"tid", EventLogUtils::Start}, {"mode", 1}, {"version", VERSION}, - {"camera_connected", DataManager::instance()->getdevStatus() ? true : false} + {"camera_connected", DataManager::instance()->getdevStatus() == CAM_CANUSE} }; EventLogUtils::get().writeLogs(obj); } @@ -1273,7 +1284,7 @@ void CMainWindow::updateBlockSystem(bool bTrue) } else { QDBusReply tmp = m_reply; m_reply = QDBusReply(); - qDebug() << "Nublock shutdown."; + qDebug() << "Unblock shutdown."; } } if (m_bWayland) { @@ -1284,13 +1295,14 @@ void CMainWindow::updateBlockSystem(bool bTrue) } else { QDBusReply tmp = m_replySleep; m_replySleep = QDBusReply(); - qDebug() << "Nublock sleep."; + qDebug() << "Unblock sleep."; } } } void CMainWindow::onNoCam() { + qDebug() << __func__; m_pTitlebar->titlebar()->setBackgroundTransparent(false); m_pTitlebar->slotThemeTypeChanged(); showChildWidget(); @@ -1299,6 +1311,7 @@ void CMainWindow::onNoCam() void CMainWindow::stopCancelContinuousRecording(bool bTrue) { + qDebug() << __func__ << bTrue; if (bTrue) { onStopPhotoAndRecord(); } @@ -1378,6 +1391,7 @@ void CMainWindow::onDirectoryChanged(const QString &filePath) void CMainWindow::onSwitchCameraSuccess(const QString &cameraName) { + qWarning() << __func__ << cameraName; QStringList tmpList = cameraName.split(":"); if (!tmpList.isEmpty()) { m_labelCameraName->setText(tmpList[0]); @@ -1387,6 +1401,7 @@ void CMainWindow::onSwitchCameraSuccess(const QString &cameraName) m_labelCameraName->show(); m_filterName->hide(); m_showCameraNameTimer->start(); + showChildWidget(); } void CMainWindow::onTimeoutLock(const QString &serviceName, QVariantMap key2value, QStringList) @@ -1417,6 +1432,9 @@ void CMainWindow::onTimeoutLock(const QString &serviceName, QVariantMap key2valu onStopPhotoAndRecord(); } // } + + // 锁屏时,停用摄像头,因为用户可能需要人脸解锁。解锁后,恢复使用摄像头。 + m_videoPre->onLockedScreen(key2value.value("Locked").value()); } void CMainWindow::onTrashFile(const QString &fileName) @@ -1463,21 +1481,21 @@ void CMainWindow::onPhotoRecordBtnClked() QJsonObject obj { {"tid", m_photoRecordBtn->photoState() ? EventLogUtils::StartPhoto : EventLogUtils::StartRecording}, {"version", VERSION}, - {"camera_connected", DataManager::instance()->getdevStatus() ? true : false} + {"camera_connected", DataManager::instance()->getdevStatus() == CAM_CANUSE} }; if(!m_bRecording) EventLogUtils::get().writeLogs(obj); - //没有摄像机,不进行任何操作 - if (NOCAM == DataManager::instance()->getdevStatus()) { - return; - } //拍照模式下 if (true == m_photoRecordBtn->photoState()) { //正在拍照 if (photoNormal != m_photoState) { m_videoPre->onTakePic(false); } else { + //摄像头不可用时,不开启新的拍照动作 + if (CAM_CANUSE != DataManager::instance()->getdevStatus()) { + return; + } int nContinuous = Settings::get().getOption("photosetting.photosnumber.takephotos").toInt(); if (nContinuous > 1) { // 仅在连拍模式下才开启窗口状态监听线程 @@ -1491,6 +1509,10 @@ void CMainWindow::onPhotoRecordBtnClked() if (true == m_bRecording) { m_videoPre->onEndBtnClicked(); } else { + //摄像头不可用时,不开启新的录像动作 + if (CAM_CANUSE != DataManager::instance()->getdevStatus()) { + return; + } QFileInfo fi(m_videoPath); if (!fi.isWritable()) return; @@ -1867,6 +1889,7 @@ void CMainWindow::initConnection() connect(m_videoPre, SIGNAL(noCamAvailable()), this, SLOT(onNoCam())); connect(m_videoPre, &videowidget::camAvailable, this, [=](){ m_pTitlebar->titlebar()->setBackgroundTransparent(true); + m_pTitlebar->slotThemeTypeChanged(); }); //设置新的分辨率 connect(m_videoPre, SIGNAL(sigDeviceChange()), &Settings::get(), SLOT(setNewResolutionList())); @@ -1944,7 +1967,7 @@ void CMainWindow::initRightButtons() m_cameraSwitchBtn->setToolTip(tr("Switch Cameras")); m_photoRecordBtn = new photoRecordBtn(this); - m_photoRecordBtn->setFixedSize(photeRecordDiam, photeRecordDiam); + m_photoRecordBtn->setFixedSize(photoRecordDiam, photoRecordDiam); m_photoRecordBtn->setObjectName(BUTTON_PICTURE_VIDEO); m_photoRecordBtn->setAccessibleName(BUTTON_PICTURE_VIDEO); m_photoRecordBtn->setToolTip(tr("Photo")); @@ -1981,6 +2004,7 @@ void CMainWindow::initRightButtons() connect(&m_fileWatcherUp, SIGNAL(fileChanged(const QString &)), this, SLOT(onDirectoryChanged(const QString &))); //摄像头切换成功信号 connect(m_videoPre, SIGNAL(switchCameraSuccess(const QString &)), this, SLOT(onSwitchCameraSuccess(const QString &))); + connect(m_videoPre, SIGNAL(switchCameraSuccess(const QString &)), &Settings::get(), SLOT(setNewResolutionList())); locateRightButtons(); } @@ -1989,10 +2013,10 @@ void CMainWindow::locateRightButtons() if (nullptr == m_photoRecordBtn) { return; } - int buttonCenterX = width() - rightOffset - photeRecordDiam / 2; + int buttonCenterX = width() - rightOffset - photoRecordDiam / 2; int buttonCenterY = height() / 2; - int photoRecordLeftX = buttonCenterX - photeRecordDiam / 2; - int photoRecordLeftY = buttonCenterY - photeRecordDiam / 2; + int photoRecordLeftX = buttonCenterX - photoRecordDiam / 2; + int photoRecordLeftY = buttonCenterY - photoRecordDiam / 2; m_photoRecordBtn->move(photoRecordLeftX, photoRecordLeftY); int switchCameraOffset = height() / 15; @@ -2001,7 +2025,7 @@ void CMainWindow::locateRightButtons() m_cameraSwitchBtn->move(switchCameraX, switchCameraY); int switchBtnX = buttonCenterX - switchBtnWidth / 2; - int switchBtnY = photoRecordLeftY + photeRecordDiam; + int switchBtnY = photoRecordLeftY + photoRecordDiam; m_modeSwitchBox->move(switchBtnX, switchBtnY); int snapLabelOffset = height() / 10; @@ -2025,6 +2049,11 @@ void CMainWindow::onLocalTimeChanged() void CMainWindow::setSelBtnShow() { + // 有效设备个数小于等于1,不显示切换按钮 + if (m_videoPre->getValidDeviceNum() <= 1) { + return; + } + m_bSwitchCameraShowEnable = true; if (m_cameraSwitchBtn->isHidden()) { showChildWidget(); @@ -2205,6 +2234,13 @@ void CMainWindow::onSettingsDlgClose() if (bPathChanged) { reflushSnapshotLabel(); } + + // 8k分辨率时不显示录像功能 + bool isResolutionTooHigh = checkResolutionTooHigh(); + if (isResolutionTooHigh) { + m_modeSwitchBox->reset(); + } + showChildWidget(); } void CMainWindow::onEnableSettings(bool bTrue) @@ -2316,9 +2352,12 @@ void CMainWindow::showChildWidget() showWidget(m_photoRecordBtn, true); return; } + + // 8k分辨率时不显示录像功能 + bool isResolutionTooHigh = checkResolutionTooHigh(); showWidget(m_snapshotLabel, true); showWidget(m_cameraSwitchBtn, m_bSwitchCameraShowEnable); - showWidget(m_modeSwitchBox, true); + showWidget(m_modeSwitchBox, !isResolutionTooHigh); showWidget(m_photoRecordBtn, true); showWidget(m_takePhotoSettingArea, true); showWidget(m_filterName, m_bShowFilterName); @@ -2337,6 +2376,21 @@ void CMainWindow::showWidget(DWidget *widget, bool bShow) } } +bool CMainWindow::checkResolutionTooHigh() const +{ + int currentWidth = 0; + int currentHeight = 0; + v4l2_dev_t *fd = get_v4l2_device_handler(); + if (fd) { + currentWidth = v4l2core_get_frame_width(fd); + currentHeight = v4l2core_get_frame_height(fd); + } + bool isTrue = currentWidth > MAX_WIDTH_LIMIT || currentHeight > MAX_HEIGHT_LIMIT; + qInfo() << __func__ << isTrue << currentWidth << "x" << currentHeight; + + return isTrue; +} + CMainWindow::~CMainWindow() { if (m_devnumMonitor) { diff --git a/src/src/mainwindow.h b/src/src/mainwindow.h index d76d46a31..ec7d32c9a 100644 --- a/src/src/mainwindow.h +++ b/src/src/mainwindow.h @@ -201,6 +201,11 @@ class CMainWindow : public DMainWindow */ void getMediaFileInfoList(const QString &path, QFileInfoList &fileList); + /** + * @brief 检测分辨率是否太高,8K分辨率不显示录像功能 + */ + bool checkResolutionTooHigh() const; + private slots: /** * @brief setSelBtnHide 设置切换相机按钮隐藏 diff --git a/src/src/majorimageprocessingthread.cpp b/src/src/majorimageprocessingthread.cpp index 1aef0dc39..644bb135a 100644 --- a/src/src/majorimageprocessingthread.cpp +++ b/src/src/majorimageprocessingthread.cpp @@ -34,11 +34,13 @@ MajorImageProcessingThread::MajorImageProcessingThread():m_bHorizontalMirror(fal void MajorImageProcessingThread::stop() { + qDebug() << __func__; m_stopped = 1; } void MajorImageProcessingThread::init() { + qDebug() << __func__; m_stopped = 0; m_majorindex = -1; m_frame = nullptr; @@ -138,6 +140,7 @@ void MajorImageProcessingThread::processingImage(QImage& img) void MajorImageProcessingThread::run() { + qDebug() << __func__; if (m_eEncodeEnv != QCamera_Env) { m_videoDevice = get_v4l2_device_handler(); v4l2core_start_stream(m_videoDevice); @@ -191,7 +194,21 @@ void MajorImageProcessingThread::run() } m_result = -1; - m_frame = v4l2core_get_decoded_frame(m_videoDevice); + if (config_get()->verbosity > 0) { + static QElapsedTimer decodeTimer; + static int decodeCount = 0; + if (decodeCount % LOGGING_INTERVAL == LOGGING_INTERVAL - 1) { + decodeTimer.start(); + m_frame = v4l2core_get_decoded_frame(m_videoDevice); + qDebug() << "v4l2 decoded time:" << decodeTimer.elapsed() << "ms"; + decodeCount = 0; + } else { + m_frame = v4l2core_get_decoded_frame(m_videoDevice); + decodeCount++; + } + } else { + m_frame = v4l2core_get_decoded_frame(m_videoDevice); + } if (m_frame == nullptr) { framedely++; @@ -265,6 +282,14 @@ void MajorImageProcessingThread::run() if (GStreamer_Env == m_eEncodeEnv) bUseRgb = true; + // DConfig配置控制RGB数据使用模式,-1表示不设置(由系统自动判断),0表示强制关闭,1表示强制开启 + int useRgbData = DataManager::instance()->getUseRgbData(); + if (useRgbData == 1) { + bUseRgb = true; + } else if (useRgbData == 0) { + bUseRgb = false; + } + if (bUseRgb || (m_bPhoto && m_filtersGroupDislay)) { if (m_nVdWidth != static_cast(m_frame->width) || m_nVdHeight != static_cast(m_frame->height)) { m_nVdWidth = static_cast(m_frame->width); @@ -334,10 +359,13 @@ void MajorImageProcessingThread::run() if (!get_capture_pause()) { //设置时间戳 set_video_timestamptmp(static_cast(m_frame->timestamp)); + if (m_firstPts == 0) { m_firstPts = m_frame->timestamp; } m_nCount = (m_frame->timestamp - m_firstPts) / 1000000000; + + lasttimestamp = m_frame->timestamp; encoder_add_video_frame(input_frame, size, static_cast(m_frame->timestamp), m_frame->isKeyframe); } else { //设置暂停时长 @@ -467,7 +495,9 @@ void MajorImageProcessingThread::run() break; #endif //保证画面流畅的前提下降低刷新率 - msleep(33); + if (!DataManager::instance()->isPreviewNoDelay() && m_nVdWidth <= 1920) { + msleep(33); + } } v4l2core_stop_stream(m_videoDevice); diff --git a/src/src/majorimageprocessingthread.h b/src/src/majorimageprocessingthread.h index 109df5610..5376ae4bc 100644 --- a/src/src/majorimageprocessingthread.h +++ b/src/src/majorimageprocessingthread.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "datamanager.h" @@ -191,6 +192,8 @@ public slots: int m_nCount; uint64_t m_firstPts; + uint64_t lasttimestamp; + QImage m_Img; //mips、wayland下使用该变量 QImage m_filterImg; //滤镜预览类使用 大小40*40 QImage m_jpgImage; // 从v4l2获取的jpg格式的视频帧图片 diff --git a/src/src/photorecordbtn.cpp b/src/src/photorecordbtn.cpp index 6fc059689..c70fda940 100644 --- a/src/src/photorecordbtn.cpp +++ b/src/src/photorecordbtn.cpp @@ -132,7 +132,7 @@ void photoRecordBtn::mousePressEvent(QMouseEvent *event) void photoRecordBtn::mouseMoveEvent(QMouseEvent *event) { - //解决bug 在按钮中可拖动相机界面,https://pms.uniontech.com/zentao/bug-view-100647.html + //解决bug 在按钮中可拖动相机界面,bug 100647 Q_UNUSED(event); return; } diff --git a/src/src/previewopenglwidget.cpp b/src/src/previewopenglwidget.cpp index 40b5c2390..8f5e2ddcf 100644 --- a/src/src/previewopenglwidget.cpp +++ b/src/src/previewopenglwidget.cpp @@ -82,9 +82,9 @@ void PreviewOpenglWidget::initializeGL() }"; const char *fsrc; - - if (get_wayland_status() == 0) { - fsrc = "varying vec2 textureOut; \ + if (1 == is_forceGles()) { + fsrc = "precision mediump float; \ + varying vec2 textureOut; \ uniform sampler2D tex_y; \ uniform sampler2D tex_u; \ uniform sampler2D tex_v; \ @@ -100,11 +100,8 @@ void PreviewOpenglWidget::initializeGL() 1.13983, -0.58060, 0) * yuv; \ gl_FragColor = vec4(rgb, 1); \ }"; - } else { - fsrc = "#ifdef GL_ES \ - precision mediump float; \ - #endif \ - varying vec2 textureOut; \ + } else if (get_wayland_status() == 0) { + fsrc = "varying vec2 textureOut; \ uniform sampler2D tex_y; \ uniform sampler2D tex_u; \ uniform sampler2D tex_v; \ @@ -152,6 +149,7 @@ void PreviewOpenglWidget::initializeGL() void PreviewOpenglWidget::resizeGL(int w, int h) { glViewport(0, 0, w, h); + glClearColor(0.0, 0.0, 0.0, 1.0); update(); } @@ -159,12 +157,20 @@ void PreviewOpenglWidget::paintGL() { if (m_yuvPtr == nullptr) return; - + bool bReBuildImage = false; + if(m_imgSize != QSize(m_videoWidth, m_videoHeight)) { + bReBuildImage = true; + m_imgSize = QSize(m_videoWidth, m_videoHeight); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } glActiveTexture(GL_TEXTURE0); //激活纹理单元GL_TEXTURE0,系统里面的 glBindTexture(GL_TEXTURE_2D, m_idY); //绑定y分量纹理对象id到激活的纹理单元 //使用内存中的数据创建真正的y分量纹理数据,https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, static_cast(m_videoWidth), static_cast(m_videoHeight), 0, GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr); + if( bReBuildImage ) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, static_cast(m_videoWidth), static_cast(m_videoHeight), 0, GL_RED, GL_UNSIGNED_BYTE, 0); + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(m_videoWidth), static_cast(m_videoHeight), GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -174,7 +180,10 @@ void PreviewOpenglWidget::paintGL() glBindTexture(GL_TEXTURE_2D, m_idU); //使用内存中的数据创建真正的u分量纹理数据 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth >> 1, m_videoHeight >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr + m_videoWidth * m_videoHeight); + if( bReBuildImage ) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth >> 1, m_videoHeight >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 0); + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_videoWidth >> 1, m_videoHeight >> 1, GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr + m_videoWidth * m_videoHeight); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -184,7 +193,10 @@ void PreviewOpenglWidget::paintGL() glBindTexture(GL_TEXTURE_2D, m_idV); //使用内存中的数据创建真正的v分量纹理数据 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth >> 1, m_videoHeight >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr + m_videoWidth * m_videoHeight * 5 / 4); + if( bReBuildImage ) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_videoWidth >> 1, m_videoHeight >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 0); + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_videoWidth >> 1, m_videoHeight >> 1, GL_RED, GL_UNSIGNED_BYTE, m_yuvPtr + m_videoWidth * m_videoHeight * 5 / 4); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); diff --git a/src/src/previewopenglwidget.h b/src/src/previewopenglwidget.h index 1fb0789b6..554eeda94 100644 --- a/src/src/previewopenglwidget.h +++ b/src/src/previewopenglwidget.h @@ -91,6 +91,7 @@ public slots: uint m_videoHeight; uchar *m_yuvPtr; + QSize m_imgSize; }; #endif // PREVIEWOPENGLWIDGET_H diff --git a/src/src/rollingbox.cpp b/src/src/rollingbox.cpp index 62107ec3e..f29e3dfa8 100644 --- a/src/src/rollingbox.cpp +++ b/src/src/rollingbox.cpp @@ -301,3 +301,10 @@ void RollingBox::setDeviation(int n) update(); } +void RollingBox::reset() +{ + m_currentIndex = m_rangeMin; + m_deviation = 0; + emit currentValueChanged(m_currentIndex); + update(); +} diff --git a/src/src/rollingbox.h b/src/src/rollingbox.h index 3e381e953..c323a8656 100644 --- a/src/src/rollingbox.h +++ b/src/src/rollingbox.h @@ -36,6 +36,11 @@ class RollingBox : public QWidget */ int getCurrentValue(); + /** + * @brief 重置至初始位置 + */ + void reset(); + protected: /** * @brief 鼠标按下事件 diff --git a/src/src/takephotosettingareawidget.cpp b/src/src/takephotosettingareawidget.cpp index 3297e8b53..8465890a1 100644 --- a/src/src/takephotosettingareawidget.cpp +++ b/src/src/takephotosettingareawidget.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -152,7 +152,7 @@ void takePhotoSettingAreaWidget::initButtons() m_scrollAreaWidget->setAttribute(Qt::WA_TranslucentBackground, true); QVBoxLayout *scrollLayout = new QVBoxLayout(m_scrollAreaWidget); scrollLayout->setContentsMargins(0, 0, 0, 0); - scrollLayout->setSpacing(2); + scrollLayout->setSpacing(0); m_scrollAreaWidget->setLayout(scrollLayout); m_scrollArea = new QScrollArea(this); m_scrollArea->setFrameShape(QFrame::NoFrame); @@ -1545,6 +1545,10 @@ void takePhotoSettingAreaWidget::setState(bool bPhoto) m_flashlightUnfoldBtn->setOpacity(102); m_filtersUnfoldBtn->setOpacity(102); m_exposureBtn->setOpacity(102); + m_flashlightUnfoldBtn->setIconOpacity(1); + m_delayUnfoldBtn->setIconOpacity(1); + m_filtersUnfoldBtn->setIconOpacity(1); + m_exposureBtn->setIconOpacity(1); } else { m_flashlightUnfoldBtn->setVisible(false); m_filtersUnfoldBtn->setVisible(false); diff --git a/src/src/titlebar.cpp b/src/src/titlebar.cpp index 36404c130..ae1b0368d 100644 --- a/src/src/titlebar.cpp +++ b/src/src/titlebar.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -27,7 +27,10 @@ Titlebar::Titlebar(QWidget *parent) : DBlurEffectWidget(parent), d_ptr(new Title { Q_D(Titlebar); - setAttribute(Qt::WA_TranslucentBackground, true); + // 设置DBlurEffectWidget为完全透明 + setBlurEnabled(false); // 关闭模糊效果 + setMaskAlpha(0); // 设置遮罩完全透明 + setMaskColor(QColor(0, 0, 0, 0)); // 设置遮罩颜色为完全透明 QHBoxLayout *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -39,6 +42,13 @@ Titlebar::Titlebar(QWidget *parent) : DBlurEffectWidget(parent), d_ptr(new Title d->m_titlebar->setBlurBackground(false); layout->addWidget(d->m_titlebar); + d->m_shadowEffect = new QGraphicsDropShadowEffect(this); + d->m_shadowEffect->setOffset(d->offsetX, d->offsetX); + d->m_shadowEffect->setBlurRadius(d->offsetX); + d->m_shadowEffect->setColor(Qt::transparent); + + this->setGraphicsEffect(d->m_shadowEffect); + connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &Titlebar::slotThemeTypeChanged); } @@ -52,7 +62,7 @@ void Titlebar::slotThemeTypeChanged() { Q_D(const Titlebar); QPalette pa; - if(DataManager::instance()->getdevStatus() != NOCAM) { + if(DataManager::instance()->getdevStatus() == CAM_CANUSE) { pa.setColor(QPalette::ButtonText, d->lightColor); d->m_titlebar->setPalette(pa); } else{ @@ -72,20 +82,14 @@ void Titlebar::paintEvent(QPaintEvent *pe) QPainter painter(this); QPalette pa; - QPen pen(QColor(0, 0, 0, 0)); - QLinearGradient linearGradient(width(), 0, width(), height()); - - linearGradient.setColorAt(0, QColor(0, 0, 0, 255 * 0.5)); //垂直线性渐变 - linearGradient.setColorAt(1, QColor(0, 0, 0, 0)); - if(DataManager::instance()->getdevStatus() != NOCAM) { + if(DataManager::instance()->getdevStatus() == CAM_CANUSE) { pa.setColor(QPalette::ButtonText, d->lightColor); d->m_titlebar->setPalette(pa); } painter.setRenderHint(QPainter::Antialiasing, true); - painter.setBrush(QBrush(linearGradient)); - painter.setPen(pen); + painter.setPen(Qt::transparent); painter.drawRect(rect()); } diff --git a/src/src/videowidget.cpp b/src/src/videowidget.cpp index 490c34f97..0e709d7e4 100644 --- a/src/src/videowidget.cpp +++ b/src/src/videowidget.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co.,Ltd. +// Copyright (C) 2020 ~ 2026 Uniontech Software Technology Co.,Ltd. // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -32,15 +32,16 @@ #include #include #include +#include #define COUNTDOWN_WIDTH 32 #define COUNTDOWN_HEIGHT 61 -#define COUNTDOWN_OFFECT 20 +#define COUNTDOWN_OFFSET 20 -static PRIVIEW_ENUM_STATE g_Enum_Camera_State = PICTRUE; +static PREVIEW_ENUM_STATE g_Enum_Camera_State = PICTURE; // 重写QGraphicsView类,在子类中,直接将鼠标事件路由到QWidget,这样QGraphicsScene不会接收和处理鼠标事件 -// bug链接 https://pms.uniontech.com/zentao/bug-view-100791.html +// bug 100791 QGraphicsViewEx::QGraphicsViewEx(QWidget *parent) : QGraphicsView(parent) { @@ -83,7 +84,14 @@ videowidget::videowidget(DWidget *parent) m_pNormalItem->setZValue(0); m_bActive = false; //是否录制中 - m_takePicSound = new QSound(":/resource/Camera.wav"); + m_takePicSound = new QSoundEffect(this); + m_takePicSound->setSource(QUrl("qrc:/resource/Camera.wav")); + m_takePicSound->setLoopCount(1); + m_takePicSound->setVolume(1.0); + connect(m_takePicSound, &QSoundEffect::playingChanged, + this, &videowidget::onShutterSoundPlayingChanged); + connect(m_takePicSound, &QSoundEffect::statusChanged, + this, &videowidget::onShutterSoundStatusChanged); m_countTimer = new QTimer(this); m_flashTimer = new QTimer(this); m_recordingTimer = new QTimer(this); @@ -120,6 +128,10 @@ videowidget::videowidget(DWidget *parent) // 画布错误提示图元 m_pCamErrItem = new QGraphicsTextItem; m_pCamErrItem->setZValue(2); + m_pCamErrItemSub = new QGraphicsTextItem; + m_pCamErrItemSub->setTextWidth(370); + m_pCamErrItemSub->document()->setDefaultTextOption(QTextOption(Qt::AlignCenter)); // 设置文本居中 + m_pCamErrItemSub->setZValue(2); m_pGridLayout = new QGridLayout(this); DLabel *recordingRedStatus = new DLabel;//录制状态红点 @@ -143,6 +155,7 @@ videowidget::videowidget(DWidget *parent) m_pNormalScene->addItem(m_pNormalItem); m_pNormalScene->addItem(m_pSvgItem); m_pNormalScene->addItem(m_pCamErrItem); + m_pNormalScene->addItem(m_pCamErrItemSub); m_pNormalScene->addItem(m_pGridLineItem); recordingwidgetlay->setSpacing(0); @@ -270,8 +283,18 @@ void videowidget::delayInit() m_flashLabel->hide(); QString device = dc::Settings::get().getBackOption("device").toString(); - //启动视频 - switchCamera(device.toStdString().c_str(), ""); + // 启动视频预览 + // 如果配置的设备无效,切换到第一个有效的设备 + // 如果有效的设备也不存在,走默认流程 + updateValidDevices(); + if (!isDeviceValidByDevice(device)) { + QString validDevice = getFirstValidDevice(); + qWarning() << "INVALID device from config:" << device << ", found first valid device:" << validDevice; + switchCamera(validDevice.toStdString().c_str(), ""); + } else { + qInfo() << "VALID device from config:" << device; + switchCamera(device.toStdString().c_str(), ""); // 走默认逻辑 + } QObject::connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &videowidget::onThemeTypeChanged); @@ -279,11 +302,14 @@ void videowidget::delayInit() void videowidget::showNocam() { + qDebug() << __func__; if (!m_pNormalView->isVisible()) m_pNormalView->show(); - if (!m_pCamErrItem->isVisible()) + if (!m_pCamErrItem->isVisible()) { m_pCamErrItem->show(); + m_pCamErrItemSub->show(); + } if (!m_pSvgItem->isVisible()) m_pSvgItem->show(); @@ -294,24 +320,39 @@ void videowidget::showNocam() m_openglwidget->hide(); if (m_GridType != Grid_None) { - if (!m_pGridLineItem->isVisible()) - m_pGridLineItem->show(); + // 提示界面不显示井字格 + if (m_pGridLineItem->isVisible()) { + m_pGridLineItem->hide(); + } if (m_gridlinewidget->isVisible()) m_gridlinewidget->hide(); } emit sigDeviceChange(); + QString strsub; if (DGuiApplicationHelper::LightType == DGuiApplicationHelper::instance()->themeType()) { - m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/Not connected.svg"), this)); + if (DataManager::instance()->isSupportCameraSwitch()) { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/camera_switch_not_on.svg"), this)); + strsub = tr("Your camera might not be turned on. If you need to use it, please check whether the hardware switch of the camera or the camera hotkey on the keyboard is enabled"); + } else { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/Not connected.svg"), this)); + } QColor clr(255, 255, 255); clr.setAlphaF(0.8); m_pCamErrItem->setDefaultTextColor(clr); + m_pCamErrItemSub->setDefaultTextColor(clr); } else { - m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/Not connected_dark.svg"), this)); + if (DataManager::instance()->isSupportCameraSwitch()) { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/camera_switch_not_on_dark.svg"), this)); + strsub = tr("Your camera might not be turned on. If you need to use it, please check whether the hardware switch of the camera or the camera hotkey on the keyboard is enabled"); + } else { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/Not connected_dark.svg"), this)); + } QColor clr(Qt::white); clr.setAlphaF(0.2); m_pCamErrItem->setDefaultTextColor(clr); + m_pCamErrItemSub->setDefaultTextColor(clr); } QFont ft("SourceHanSansSC"); @@ -320,12 +361,17 @@ void videowidget::showNocam() m_pCamErrItem->setFont(ft); QString str(tr("No webcam found"));//未连接摄像头 m_pCamErrItem->setPlainText(str); + QFont ftsub("SourceHanSansSC"); + ftsub.setPixelSize(12); + m_pCamErrItemSub->setFont(ftsub); + m_pCamErrItemSub->setPlainText(strsub); m_pNormalScene->setSceneRect(m_pSvgItem->boundingRect()); m_flashLabel->hide(); itemPosChange(); m_pCamErrItem->show(); + m_pCamErrItemSub->show(); m_pSvgItem->show(); emit noCam(); @@ -347,8 +393,10 @@ void videowidget::showCamUsed() m_openglwidget->hide(); if (m_GridType != Grid_None) { - if (!m_pGridLineItem->isVisible()) - m_pGridLineItem->show(); + // 提示界面不显示井字格 + if (m_pGridLineItem->isVisible()) { + m_pGridLineItem->hide(); + } if (m_gridlinewidget->isVisible()) m_gridlinewidget->hide(); } @@ -361,24 +409,32 @@ void videowidget::showCamUsed() clrText.setAlphaF(0.8); m_pCamErrItem->setDefaultTextColor(clrText);//浅色主题文字和图片是白色,特殊处理 m_pCamErrItem->setPlainText(str); + m_pCamErrItemSub->setDefaultTextColor(clrText); + m_pCamErrItemSub->setPlainText(QString()); } else { m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/Take up_dark.svg"), this)); QColor clrText(Qt::white); clrText.setAlphaF(0.2); m_pCamErrItem->setDefaultTextColor(clrText); m_pCamErrItem->setPlainText(str); + m_pCamErrItemSub->setDefaultTextColor(clrText); + m_pCamErrItemSub->setPlainText(QString()); } QFont ft("SourceHanSansSC"); ft.setWeight(QFont::DemiBold); ft.setPixelSize(17); m_pCamErrItem->setFont(ft); + QFont ftsub("SourceHanSansSC"); + ftsub.setPixelSize(12); + m_pCamErrItemSub->setFont(ftsub); m_pNormalScene->setSceneRect(m_pSvgItem->boundingRect()); m_flashLabel->hide(); itemPosChange(); m_pCamErrItem->show(); + m_pCamErrItemSub->show(); m_pSvgItem->show(); emit noCamAvailable(); @@ -388,10 +444,13 @@ void videowidget::showCamUsed() #ifndef __mips__ void videowidget::ReceiveOpenGLstatus(bool result) { + //qDebug() << "ReceiveOpenGLstatus" << result; if (result && m_openglwidget) { //Success - if (m_pCamErrItem->isVisible()) + if (m_pCamErrItem->isVisible()) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible()) m_pSvgItem->hide(); @@ -429,7 +488,7 @@ void videowidget::ReceiveOpenGLstatus(bool result) } } - if (!m_openglwidget->isVisible()) + if (!m_openglwidget->isVisible() && !m_isFlash) m_openglwidget->show(); malloc_trim(0); @@ -452,8 +511,11 @@ void videowidget::ReceiveMajorImage(QImage *image, int result) switch (result) { case 0: //Success // m_imgPrcThread->m_rwMtxImg.lock(); - m_pNormalView->show(); + + if (!m_isFlash) + m_pNormalView->show(); m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); m_pSvgItem->hide(); m_pNormalItem->show(); @@ -499,6 +561,15 @@ void videowidget::ReceiveMajorImage(QImage *image, int result) } } +void videowidget::onLockedScreen(bool bLocked) +{ + m_isLockedScreen = bLocked; + if (m_isLockedScreen) { + // 复用现有函数,达到关闭摄像头的效果 + this->onReachMaxDelayedFrames(); + } +} + void videowidget::onReachMaxDelayedFrames() { check_device_list_events(get_v4l2_device_handler()); @@ -522,16 +593,17 @@ void videowidget::onReachMaxDelayedFrames() m_openglwidget->hide(); } -void videowidget::showCountDownLabel(PRIVIEW_ENUM_STATE state) +void videowidget::showCountDownLabel(PREVIEW_ENUM_STATE state) { QString str; switch (state) { - case PICTRUE: + case PICTURE: m_recordingTimeWidget->hide(); break; case VIDEO: m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); m_pSvgItem->hide(); if (!get_capture_pause()) {//判断是否是暂停状态 @@ -584,6 +656,7 @@ void videowidget::showCountDownLabel(PRIVIEW_ENUM_STATE state) default: m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); m_pSvgItem->hide(); m_recordingTimeWidget->hide(); break; @@ -600,24 +673,32 @@ void videowidget::resizeEvent(QResizeEvent *size) height() - m_recordingTimeWidget->height() - 15); m_dLabel->move((width() - m_dLabel->width()) / 2, - (height() - m_dLabel->height()) - COUNTDOWN_OFFECT); + (height() - m_dLabel->height()) - COUNTDOWN_OFFSET); if (m_pCamErrItem->isVisible()) { itemPosChange(); m_pCamErrItem->show(); + m_pCamErrItemSub->show(); m_pSvgItem->show(); } else { itemPosChange(); m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); } if (m_gridlinewidget) m_gridlinewidget->resize(rect().size()); +#ifndef __mips__ + if(m_openglwidget && m_openglwidget->isVisible()) { + ReceiveOpenGLstatus(true); + } +#endif } void videowidget::showCountdown() { + qDebug() << __func__; if (DataManager::instance()->getdevStatus() == NOCAM) { if (m_flashTimer->isActive()) @@ -651,7 +732,7 @@ void videowidget::showCountdown() emit updateRecordState(photoRecordBtn::Recording); } - if (g_Enum_Camera_State == PICTRUE) { + if (g_Enum_Camera_State == PICTURE) { if (m_nInterval == 0 && m_curTakePicTime >= 0) { if (m_flashEnable && 1 == m_nMaxContinuous) { int index = QApplication::desktop()->screenNumber(this); @@ -663,8 +744,10 @@ void videowidget::showCountdown() m_flashTimer->start(500); qDebug() << "flashTimer->start();"; - if (m_pNormalView) + if (m_pNormalView) { + m_isFlash = true; m_pNormalView->hide(); + } if (m_openglwidget) m_openglwidget->hide(); if (m_gridlinewidget) @@ -743,6 +826,7 @@ void videowidget::showCountdown() m_countTimer->start(m_nMaxInterval == 34 ? 0 : 1000); m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); m_pSvgItem->hide(); m_pNormalView->hide(); } @@ -750,7 +834,7 @@ void videowidget::showCountdown() } else { m_dLabel->setText(QString::number(m_nInterval)); m_dLabel->show(); - showCountDownLabel(PICTRUE); //拍照录像都要显示倒计时 + showCountDownLabel(PICTURE); //拍照录像都要显示倒计时 m_nInterval--; qInfo() << "m_nInterval:" << m_nInterval; } @@ -831,17 +915,70 @@ void videowidget::showRecTime() m_recordingTime->setText(strTime); } +void videowidget::startShutterSoundWarmup() +{ + if (m_shutterSoundWarming || m_takePicSound->status() != QSoundEffect::Ready) { + return; + } + + m_shutterSoundWarming = true; + m_takePicSound->setVolume(0.0); + m_takePicSound->play(); +} + +void videowidget::onShutterSoundPlayingChanged() +{ + if (!m_shutterSoundWarming) { + return; + } + + if (m_takePicSound->isPlaying()) { + m_takePicSound->stop(); + return; + } + + m_shutterSoundWarming = false; + m_takePicSound->setVolume(1.0); + if (m_shutterSoundPending && get_sound_of_takeing_photo()) { + m_shutterSoundPending = false; + QTimer::singleShot(0, this, [this] { + if (get_sound_of_takeing_photo()) { + m_takePicSound->play(); + } + }); + return; + } + m_shutterSoundPending = false; +} + +void videowidget::onShutterSoundStatusChanged() +{ + if (m_takePicSound->status() == QSoundEffect::Error) { + m_shutterSoundWarming = false; + m_shutterSoundPending = false; + m_takePicSound->setVolume(1.0); + } else if (m_shutterSoundPending) { + startShutterSoundWarmup(); + } +} + void videowidget::flash() { - if (get_sound_of_takeing_photo()) - m_takePicSound->play(); + qDebug() << __func__; + if (get_sound_of_takeing_photo()) { + m_shutterSoundPending = true; + startShutterSoundWarmup(); + } #ifndef __mips__ - if (!get_wayland_status()) + if (!get_wayland_status()) { if (!m_openglwidget->isVisible() && filter_Normal == m_filterType && 0 == m_exposure) m_openglwidget->show(); - + } else { + m_pNormalView->show(); + } #endif + m_isFlash = false; m_flashLabel->hide(); //为避免没有关闭,放到定时器里边关闭 if (m_curTakePicTime == 0) @@ -920,8 +1057,10 @@ void videowidget::onEndBtnClicked() if (m_recordingTimer->isActive()) m_recordingTimer->stop(); - if (m_pCamErrItem->isVisible() && (m_imgPrcThread->getStatus() == 0) && DataManager::instance()->encodeEnv() != QCamera_Env) + if (m_pCamErrItem->isVisible() && (m_imgPrcThread->getStatus() == 0) && DataManager::instance()->encodeEnv() != QCamera_Env) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible() && (m_imgPrcThread->getStatus() == 0) && DataManager::instance()->encodeEnv() != QCamera_Env) m_pSvgItem->hide(); @@ -1005,10 +1144,17 @@ void videowidget::onEndBtnClicked() void videowidget::onRestartDevices() { + if (m_isLockedScreen) { + qDebug() << __func__ << "bypass reason(locked screen)"; + return; + } + if (DataManager::instance()->getdevStatus() != CAM_CANUSE) { + qDebug() << "Device status:" << DataManager::instance()->getdevStatus(); onChangeDev(); if (DataManager::instance()->getdevStatus() == CAM_CANUSE) { + qDebug() << "Device status:" << DataManager::instance()->getdevStatus(); emit sigDeviceChange(); QPalette plt = palette(); plt.setColor(QPalette::Window, Qt::white); @@ -1023,6 +1169,7 @@ void videowidget::onRestartDevices() void videowidget::onChangeDev() { + qDebug() << __func__; v4l2_dev_t *devicehandler = get_v4l2_device_handler(); if (m_imgPrcThread != nullptr) @@ -1037,37 +1184,72 @@ void videowidget::onChangeDev() } v4l2_device_list_t *devlist = get_device_list(); - if (devlist->num_devices == 2) { - for (int i = 0 ; i < devlist->num_devices; i++) { - QString str1 = QString(devlist->list_devices[i].device); - if (str != str1) { - if (E_OK == switchCamera(devlist->list_devices[i].device, devlist->list_devices[i].name)) { - break; - } + if (devlist == nullptr) { + qWarning() << "get device list FAILED"; + return; + } + + // USB摄像头分组相关逻辑来自xiwo分支,是否开启由DConfig控制 + int groupNum = 1; + QVector>> vGroupData; + // 如果未启用USB摄像头分组,则分组数默认为1,保持原有逻辑; + // 如果启用USB摄像头分组,则实际获取USB分组情况,根据分组结果进行处理; + if (DataManager::instance()->isEnableUsbGroup()) { + // 必须确保 devlist 的生命周期长于 vGroupData 的使用周期。 + // 如果 devlist 在 vGroupData 使用完毕前被释放,将导致悬空指针,引发崩溃。 + // 好在 vGroupData 只在本函数中使用,不会在其他地方被引用,所以不会导致悬空指针问题。 + groupNum = getUSBCameraGroup(devlist, vGroupData); + qInfo() << __func__ << "groupNum:" << groupNum; + } + // 如果摄像头设备个数为0,分组情况就不用考虑了,直接显示无摄像头提示 + if (devlist->num_devices == 0) { + DataManager::instance()->setdevStatus(NOCAM); + showNocam(); + } else if (groupNum == 0) { + switchCamera("", ""); // 无有效设备,但是有设备,走默认逻辑 + } else if (groupNum == 1) { + if (m_validDevices.empty()) { + switchCamera("", ""); // 无有效设备,但是有设备,走默认逻辑 + } else { + int idx = getValidDeviceIndexByDevice(str); + if (idx == -1 || idx >= m_validDevices.size() - 1) { + // 获取第一个有效设备,直接切换到该设备 + const ValidDevice &dev = m_validDevices[0]; + switchCamera(dev.getDevice().toStdString().c_str(), dev.getName().toStdString().c_str()); + } else { + // 切换到当前设备的下一个有效设备 + const ValidDevice &dev = m_validDevices[idx + 1]; + switchCamera(dev.getDevice().toStdString().c_str(), dev.getName().toStdString().c_str()); } } } else { - if (devlist->num_devices == 0) { - DataManager::instance()->setdevStatus(NOCAM); - showNocam(); - } - - for (int i = 0 ; i < devlist->num_devices; i++) { - QString str1 = QString(devlist->list_devices[i].device); - - if (str == str1) { - if (i == devlist->num_devices - 1) { - switchCamera(devlist->list_devices[0].device, devlist->list_devices[0].name); - break; - } else { - switchCamera(devlist->list_devices[i + 1].device, devlist->list_devices[i + 1].name); - break; + if (groupNum == 2) { + for (int i = 0 ; i < vGroupData.count(); i++) { + const char *curDev = vGroupData[i].second[0]->device; + if (str != curDev) { + if (E_OK == switchCamera(curDev, vGroupData[i].second[0]->name)) { + break; + } } } - - if (str.isEmpty()) { - switchCamera(devlist->list_devices[0].device, devlist->list_devices[0].name); - break; + } else { + bool found = false; // 标记是否找到当前设备 + for (int i = 0 ; i < vGroupData.count(); i++) { + const char *curDev = vGroupData[i].second[0]->device; + if (str == curDev) { + found = true; + if (i == vGroupData.count() - 1) { + switchCamera(vGroupData[0].second[0]->device, vGroupData[0].second[0]->name); + break; + } else { + switchCamera(vGroupData[i + 1].second[0]->device, vGroupData[i + 1].second[0]->name); + break; + } + } + } + if (!found) { + // 未找到当前设备,切换到第一个设备 + switchCamera(vGroupData[0].second[0]->device, vGroupData[0].second[0]->name); } } } @@ -1075,6 +1257,7 @@ void videowidget::onChangeDev() int videowidget::switchCamera(const char *device, const char *devName) { + qWarning() << __func__ << device << devName; if (NULL == device) { return -1; } @@ -1084,14 +1267,13 @@ int videowidget::switchCamera(const char *device, const char *devName) } else { ret = 0; } + qInfo() << "Camera init:" << ret; if (ret == E_OK) { m_imgPrcThread->init(); m_imgPrcThread->start(); DataManager::instance()->setdevStatus(CAM_CANUSE); //切换摄像机成功,发送设备名称信号到主界面。 - if (devName && strlen(devName)) { - emit switchCameraSuccess(devName); - } + emit switchCameraSuccess(devName ? devName : ""); dc::Settings::get().setBackOption("device", device); } else if (ret == E_FORMAT_ERR) { v4l2_dev_t *vd = get_v4l2_device_handler(); @@ -1117,6 +1299,41 @@ int videowidget::switchCamera(const char *device, const char *devName) return ret; } +int videowidget::getUSBCameraGroup(v4l2_device_list_t *devlist, QVector>> &vGroupData) +{ + // 来自xiwo分支,根据location进行分组 + // 收到建议使用 QMap> 来存储分组数据,但我们担心影响现有代码逻辑, + // 所以暂时保留 QVector>> 来存储分组数据。 + if (devlist == nullptr || devlist->list_devices == nullptr || devlist->num_devices == 0) { + qWarning() << __func__ << "devlist is NULL!"; + return 0; + } + + for (int i = 0; i < devlist->num_devices; i++) { + if (!isDeviceValidByDevice(devlist->list_devices[i].device)) { + continue; // 无效设备不参与分组 + } + + QString location = QString(devlist->list_devices[i].location); + + int j = 0; + for (; j < vGroupData.count(); j++) { + if (location == vGroupData.at(j).first) { + break; + } + } + if (j == vGroupData.count()) { + QVector vList; + vList.append(&devlist->list_devices[i]); + vGroupData.append(qMakePair(location, vList)); + } else { + QVector &vlist = vGroupData[j].second; + vlist.append(&devlist->list_devices[i]); + } + } + return vGroupData.count(); +} + QString videowidget::getSaveFilePrefix() { QString filePrefix = "Camera_"; @@ -1130,15 +1347,18 @@ QString videowidget::getSaveFilePrefix() void videowidget::onTakePic(bool bTrue) { - g_Enum_Camera_State = PICTRUE; + qDebug() << __func__ << bTrue; + g_Enum_Camera_State = PICTURE; if (bTrue) { //1、重置状态 if (m_countTimer->isActive()) m_countTimer->stop(); - if (m_pCamErrItem->isVisible() && (m_imgPrcThread->getStatus() == 0)) + if (m_pCamErrItem->isVisible() && (m_imgPrcThread->getStatus() == 0)) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible() && (m_imgPrcThread->getStatus() == 0)) m_pSvgItem->hide(); @@ -1190,8 +1410,10 @@ void videowidget::onTakeVideo() //点一次开,再点一次关 if (m_countTimer->isActive()) m_countTimer->stop(); - if (m_pCamErrItem->isVisible()) + if (m_pCamErrItem->isVisible()) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible()) m_pSvgItem->hide(); @@ -1222,8 +1444,10 @@ void videowidget::onTakeVideo() //点一次开,再点一次关 if (m_countTimer->isActive()) m_countTimer->stop(); - if (m_pCamErrItem->isVisible()) + if (m_pCamErrItem->isVisible()) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible()) m_pSvgItem->hide(); @@ -1390,21 +1614,26 @@ void videowidget::startCaptureVideo() void videowidget::itemPosChange() { m_pCamErrItem->setPos((m_pSvgItem->boundingRect().width() - static_cast(m_pCamErrItem->boundingRect().width())) / 2, m_pSvgItem->boundingRect().height()); + m_pCamErrItemSub->setPos((m_pSvgItem->boundingRect().width() - static_cast(m_pCamErrItemSub->boundingRect().width())) / 2, m_pSvgItem->boundingRect().height() + m_pCamErrItem->boundingRect().height()); m_pSvgItem->update(); m_pCamErrItem->update(); + m_pCamErrItemSub->update(); } void videowidget::stopEverything() { + qDebug() << __func__; if (m_countTimer->isActive()) m_countTimer->stop(); m_flashLabel->hide(); - if (m_pCamErrItem->isVisible()) + if (m_pCamErrItem->isVisible()) { m_pCamErrItem->hide(); + m_pCamErrItemSub->hide(); + } if (m_pSvgItem->isVisible()) m_pSvgItem->hide(); @@ -1422,10 +1651,15 @@ void videowidget::stopEverything() void videowidget::onThemeTypeChanged(DGuiApplicationHelper::ColorType themeType) { if (m_pSvgItem->isVisible()) { - QString str; + QString str, strsub; if (themeType == DGuiApplicationHelper::LightType) { if (DataManager::instance()->getdevStatus() == NOCAM) { - m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/Not connected.svg"), this)); + if (DataManager::instance()->isSupportCameraSwitch()) { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/camera_switch_not_on.svg"), this)); + strsub = tr("Your camera might not be turned on. If you need to use it, please check whether the hardware switch of the camera or the camera hotkey on the keyboard is enabled"); + } else { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/Not connected.svg"), this)); + } str = tr("No webcam found");//未连接摄像头 } else {//仅CAM_CANNOT_USE m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/light/Take up.svg"), this)); @@ -1436,10 +1670,17 @@ void videowidget::onThemeTypeChanged(DGuiApplicationHelper::ColorType themeType) clrText.setAlphaF(0.8); m_pCamErrItem->setDefaultTextColor(clrText); m_pCamErrItem->setPlainText(str); + m_pCamErrItemSub->setDefaultTextColor(clrText); + m_pCamErrItemSub->setPlainText(strsub); } else if (themeType == DGuiApplicationHelper::DarkType) { if (DataManager::instance()->getdevStatus() == NOCAM) { qDebug() << "changed theme 3"; - m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/Not connected_dark.svg"), this)); + if (DataManager::instance()->isSupportCameraSwitch()) { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/camera_switch_not_on_dark.svg"), this)); + strsub = tr("Your camera might not be turned on. If you need to use it, please check whether the hardware switch of the camera or the camera hotkey on the keyboard is enabled"); + } else { + m_pSvgItem->setSharedRenderer(new QSvgRenderer(QString(":/images/icons/dark/Not connected_dark.svg"), this)); + } str = tr("No webcam found");//未连接摄像头 } else {//仅CAM_CANNOT_USE qDebug() << "changed theme 4"; @@ -1451,12 +1692,17 @@ void videowidget::onThemeTypeChanged(DGuiApplicationHelper::ColorType themeType) clor.setAlphaF(0.2); m_pCamErrItem->setDefaultTextColor(clor);//浅色主题文字和图片是白色,特殊处理 m_pCamErrItem->setPlainText(str); + m_pCamErrItemSub->setDefaultTextColor(clor); + m_pCamErrItemSub->setPlainText(strsub); } QFont ft("SourceHanSansSC"); ft.setWeight(QFont::DemiBold); ft.setPixelSize(17); m_pCamErrItem->setFont(ft); + QFont ftsub("SourceHanSansSC"); + ftsub.setPixelSize(12); + m_pCamErrItemSub->setFont(ftsub); if (DataManager::instance()->getdevStatus() == NOCAM) { m_pNormalScene->setSceneRect(m_pSvgItem->boundingRect()); itemPosChange(); @@ -1540,7 +1786,10 @@ void videowidget::setGridType(GridType type) m_pGridLineItem->hide(); } else { m_gridlinewidget->hide(); - m_pGridLineItem->show(); + // 提示界面不显示井字格 + if (!m_pSvgItem->isVisible()) { + m_pGridLineItem->show(); + } } } } @@ -1558,6 +1807,79 @@ void videowidget::onFilterDisplayChanged(int bDisplay) m_imgPrcThread->setFilterGroupState(bDisplay); } +QString videowidget::getFirstValidDevice() +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + if (m_validDevices.isEmpty()) { + qWarning() << __func__ << "no valid device!"; + return ""; + } + + // 返回第一个有效相机设备的设备节点路径 + const ValidDevice &dev = m_validDevices.first(); + qInfo() << __func__ << dev.getVid() << dev.getPid() << dev.getName() << dev.getDevice(); + return dev.getDevice(); +} + +void videowidget::updateValidDevices() +{ + qInfo() << __func__; + // 加锁,确保线程安全 + QWriteLocker locker(&m_mutexValidDevices); + m_validDevices.clear(); // 清空有效相机设备列表 + + v4l2_device_list_t *devList = get_device_list(); + if (!devList) { + qWarning() << __func__ << "get device list FAILED!"; + return; + } + + v4l2_dev_sys_data_t *v4l2_devices = devList->list_devices; + if (!v4l2_devices) { + qWarning() << __func__ << "get device list FAILED!"; + return; + } + + for (int i = 0; i < devList->num_devices; i++) { + // 获取设备的VID和PID,不足4位用0填充 + QString vid = formatDeviceId(v4l2_devices[i].vendor); + QString pid = formatDeviceId(v4l2_devices[i].product); + if (DataManager::instance()->isDeviceValid(vid, pid, v4l2_devices[i].name)) { + qInfo() << __func__ << "found valid device:" << vid << pid << v4l2_devices[i].name << v4l2_devices[i].device; + // 添加有效相机设备 + m_validDevices.push_back(ValidDevice(vid, pid, v4l2_devices[i].name, v4l2_devices[i].device)); + continue; + } + + qInfo() << __func__ << "found invalid device(blacklist):" << vid << pid << v4l2_devices[i].name << v4l2_devices[i].device; + } +} + +bool videowidget::isDeviceValidByDevice(const QString &device) +{ + return getValidDeviceIndexByDevice(device) != -1; +} + +int videowidget::getValidDeviceIndexByDevice(const QString &device) +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + for (int i = 0; i < m_validDevices.size(); i++) { + if (m_validDevices[i].getDevice() == device) { + return i; + } + } + return -1; +} + +int videowidget::getValidDeviceNum() +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + return m_validDevices.size(); +} + videowidget::~videowidget() { m_imgPrcThread->stop(); @@ -1579,6 +1901,7 @@ videowidget::~videowidget() delete m_flashLabel; m_flashLabel = nullptr; + m_takePicSound->stop(); delete m_takePicSound; m_takePicSound = nullptr; @@ -1588,9 +1911,6 @@ videowidget::~videowidget() delete m_pNormalScene; m_pNormalScene = nullptr; - delete m_takePicSound; - m_takePicSound = nullptr; - delete m_pGridLayout; m_pGridLayout = nullptr; diff --git a/src/src/videowidget.h b/src/src/videowidget.h index d769086c1..feb32a487 100644 --- a/src/src/videowidget.h +++ b/src/src/videowidget.h @@ -17,10 +17,11 @@ #include #include #include -#include +#include #include #include #include +#include #include "LPF_V4L2.h" #include "majorimageprocessingthread.h" @@ -48,9 +49,9 @@ class QGraphicsSvgItem; #define FLASH_TIME 500//拍照闪光时间,500毫秒 /** -* @brief PRIVIEW_ENUM_STATE 枚举拍照,无设备,录像 +* @brief PREVIEW_ENUM_STATE 枚举拍照,无设备,录像 */ -enum PRIVIEW_ENUM_STATE {PICTRUE, NODEVICE, VIDEO}; +enum PREVIEW_ENUM_STATE {PICTURE, NODEVICE, VIDEO}; // 重写QGraphicsView类,在子类中,直接将鼠标事件路由到QWidget,这样QGraphicsScene不会接收和处理鼠标事件 class QGraphicsViewEx : public QGraphicsView @@ -64,6 +65,40 @@ class QGraphicsViewEx : public QGraphicsView virtual void mouseMoveEvent(QMouseEvent* e) override; }; +/** +* @brief ValidDevice 有效相机设备,与黑名单机制配套使用 +*/ +class ValidDevice +{ +public: + ValidDevice() = default; + ValidDevice(const QString &_vid, const QString &_pid, const QString &_name, const QString &_device) + : vid(_vid), pid(_pid), name(_name), device(_device) {} + + bool operator==(const ValidDevice &other) const { + return vid == other.vid && pid == other.pid && + name == other.name && device == other.device; + } + + // Setter 方法 + void setVid(const QString &_vid) { vid = _vid; } + void setPid(const QString &_pid) { pid = _pid; } + void setName(const QString &_name) { name = _name; } + void setDevice(const QString &_device) { device = _device; } + + // Getter 方法 + QString getVid() const { return vid; } + QString getPid() const { return pid; } + QString getName() const { return name; } + QString getDevice() const { return device; } + +private: + QString vid; // 相机VID + QString pid; // 相机PID + QString name; // 相机名称 + QString device; // 相机设备节点路径 +}; + /** * @brief videowidget 完成拍照,录像相关工作 * 连拍,延时拍照录像,闪光灯,文件保存等 @@ -266,6 +301,11 @@ class videowidget : public DWidget */ void showCamUsed(); + /** + * @brief getValidDeviceNum 获取有效设备数量 + * @return 有效设备数量 + */ + int getValidDeviceNum(); public slots: /** * @brief onTakePic 拍照事件响应 @@ -320,6 +360,17 @@ public slots: */ void onFilterDisplayChanged(int bDisplay); + /** + * @brief 锁屏事件 + * @param bLocked 是否处于锁屏状态 + */ + void onLockedScreen(bool bLocked); + + /** + * @brief 更新有效相机设备列表 + */ + void updateValidDevices(); + private slots: /** * @brief ReceiveMajorImage 处理视频帧 mips、wayland下使用 @@ -337,16 +388,20 @@ private slots: * @brief onReachMaxDelayedFrames 达到视频帧最大延迟 */ void onReachMaxDelayedFrames(); - /** * @brief flash 闪光 */ void flash(); + void startShutterSoundWarmup(); + void onShutterSoundPlayingChanged(); + void onShutterSoundStatusChanged(); + /** * @brief slotresolutionchanged 分辨率改变槽函数 * @param 分辨率字符串(如:1920*1080) */ + void slotresolutionchanged(const QString &); /** @@ -378,7 +433,7 @@ private slots: * @brief showCountDownLabel 显示录制和拍照倒计时 * @param state 当前状态(拍照,没有设备,视频) */ - void showCountDownLabel(PRIVIEW_ENUM_STATE state); + void showCountDownLabel(PREVIEW_ENUM_STATE state); /** * @brief forbidScrollBar 禁止滚轮显示区域 @@ -414,11 +469,49 @@ private slots: */ int switchCamera(const char *device, const char *devName); + /** + * @brief 获取USB摄像头分组 + * @param devlist 设备列表 + * @param vGroupData 分组 + * @return 分组个数 + */ + int getUSBCameraGroup(v4l2_device_list_t *devlist, QVector>> &vGroupData); + /** * @brief getSaveFilePrefix * @return UOS_ 专业版 DEEPIN_ 社区版 CAMERA_ 其他 */ QString getSaveFilePrefix(); + + /** + * @brief formatDeviceId 格式化设备ID + * @param id 设备ID + * @return 格式化后的设备ID + */ + static QString formatDeviceId(uint id) { + // 格式化设备ID为4位十六进制数,不足4位用0填充 + return QString("%1").arg(id, 4, 16, QLatin1Char('0')); + } + + /** + * @brief 获取第一个有效的设备 + * @return 没找到时返回空字符串 + */ + QString getFirstValidDevice(); + + /** + * @brief 判断设备是否有效 + * @param device 设备识别路径 /dev/video* + * @return true 有效 false 无效 + */ + bool isDeviceValidByDevice(const QString &device); + + /** + * @brief 获取设备在有效设备列表中的索引 + * @param device 设备识别路径 /dev/video* + * @return 索引 无效时返回-1 + */ + int getValidDeviceIndexByDevice(const QString &device); public: MajorImageProcessingThread *m_imgPrcThread; AudioProcessingThread *m_audPrcThread; @@ -440,7 +533,9 @@ private slots: DLabel *m_recordingTime; //录制时长 QString m_videoFormat; //录制视频格式 - QSound *m_takePicSound; //拍照声音 + QSoundEffect *m_takePicSound; //拍照声音 + bool m_shutterSoundWarming = false; + bool m_shutterSoundPending = false; QString m_savePicFolder; //图片文件夹路径 QString m_saveVdFolder; //视频文件夹路径 QTimer *m_countTimer; //倒计时定时器 @@ -459,13 +554,17 @@ private slots: QGraphicsPixmapItem *m_pNormalItem; QGraphicsTextItem *m_pCamErrItem; //摄像头异常提示 + QGraphicsTextItem *m_pCamErrItemSub; //摄像头异常提示,二级提示 bool m_flashEnable; //是否闪光灯 bool m_bPhoto = true; //相机当前状态,默认为拍照状态 GstVideoWriter *m_videoWriter; //基于GStreamer实现的视频帧数据写入器 efilterType m_filterType; //当前选择的滤镜名称 int m_exposure; - + bool m_isFlash = false; + bool m_isLockedScreen = false; // 是否处于锁屏状态 + QVector m_validDevices; // 有效相机设备列表 + QReadWriteLock m_mutexValidDevices; // 有效相机设备列表读写锁 }; #endif // VIDEOWIDGET_H diff --git a/src/translations/deepin-camera_zh_CN.ts b/src/translations/deepin-camera_zh_CN.ts index fde6e9a73..84ca307a5 100644 --- a/src/translations/deepin-camera_zh_CN.ts +++ b/src/translations/deepin-camera_zh_CN.ts @@ -334,6 +334,10 @@ No webcam found 未连接摄像头 + + Your camera might not be turned on. If you need to use it, please check whether the hardware switch of the camera or the camera hotkey on the keyboard is enabled + 你的摄像头可能未开启,如需使用,请检查摄像头的硬件开关或键盘上的摄像头热键是否开启 + The webcam is in use 摄像头已被占用