范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

iOS完整推流采集音视频数据编码同步合成流

  需求
  众所周知,原始的音视频数据无法直接在网络上传输,推流需要编码后的音视频数据以合成的视频流,如flv, mov, asf流等,根据接收方需要的格式进行合成并传输,这里以合成asf流为例,讲述一个完整推流过程:即音视频从采集到编码,同步合成asf视频流,然后可以将流进行传输,为了方便,本例将合成的视频流写入一个asf文件中,以供测试.
  注意: 测试需要使用终端通过: ffplay播放demo中录制好的文件,因为asf是windows才支持的格式,mac自带播放器无法播放. 1、实现原理采集: 采集视频帧使用AVCaptureSession,采集音频帧使用Audio Unit 编码: 编码视频数据使用VideoToolbox中vtCompresssion硬编,编码音频数据使用audio converter软编. 同步: 根据时间戳生成策略 合成: 使用FFmpeg mux编码的音视频数据以合成视频流 后续: 合成好的视频流可以通过网络传输或是录制成文件 2、阅读前提音视频基础知识 推荐必读:H264, H265硬件编解码基础及码流分析 iOS视频采集实战(AVCaptureSession) Audio Unit采集音频实战 视频编码实战 音频编码实战 iOS FFmpeg环境搭建
  代码地址  : iOS完整推流
  掘金地址  : iOS完整推流
  简书地址  : iOS完整推流
  博客地址  : iOS完整推流 3、总体架构
  1.mux
  对于iOS而言,我们可以通过底层API捕获视频帧与音频帧数据,捕获视频帧使用 AVFoundation  框架中的 AVCaptureSession  , 其实它同时也可以捕获音频数据,而因为我们想使用最低延时与最高音质的音频, 所以需要借助最底层的音频捕捉框架 Audio Unit  ,然后使用 VideoToolbox  框架中的 VTCompressionSessionRef  可以对视频数据进行编码,使用 AudioConverter  可以对音频数据进行编码,我们在采集时可以将第一帧I帧产生时的系统时间作为音视频时间戳的一个起点,往后的视频说都基于此,由此可扩展做音视频同步方案,最终,我们将比那编码好的音视频数据通过FFmpeg进行合成,这里以asf流为例进行合成,并将生成好的asf流写入文件,以供测试. 生成好的asf流可直接用于网络传输. 3.1 简易流程
  采集视频 创建 AVCaptureSession  对象 指定分辨率: sessionPreset/activeFormat  ,指定帧率 setActiveVideoMinFrameDuration/setActiveVideoMaxFrameDuration  指定摄像头位置: AVCaptureDevice  指定相机其他属性: 曝光,对焦,闪光灯,手电筒等等... 将摄像头数据源加入session 指定采集视频的格式:yuv,rgb.... kCVPixelBufferPixelFormatTypeKey  将输出源加入session 创建接收视频帧队列: - (void)setSampleBufferDelegate:(nullable id)sampleBufferDelegate queue:(nullable dispatch_queue_t)sampleBufferCallbackQueue  将采集视频数据渲染到屏幕: AVCaptureVideoPreviewLayer  在回调函数中获取视频帧数据:  CMSampleBufferRef
  采集音频 配置音频格式ASBD: 采样率,声道数,采样位数,数据精度,每个包中字节数等等... 设置采样时间:  setPreferredIOBufferDuration  创建audio unit对象,指定分类.  AudioComponentInstanceNew  设置audio unit属性: 打开输入,禁止输出... 为接收的音频数据分配大小 kAudioUnitProperty_ShouldAllocateBuffer  设置接收数据的回调 开始audio unit:  AudioOutputUnitStart  在回调函数中获取音频数据:  AudioUnitRender
  编码视频数据 指定编码器宽高类型回调并创建上下文对象:  VTCompressionSessionCreate  设置编码器属性:缓存帧数, 帧率, 平均码率, 最大码率, 实时编码, 是否重排序, 配置信息, 编码模式, I帧间隔时间等. 准备编码数据:  VTCompressionSessionPrepareToEncodeFrames  开始编码:  VTCompressionSessionEncodeFrame  回调函数中获取编码后的数据 CMBlockBufferRef  根据合成码流格式,这里是asf所以需要Annex B格式,自己组装sps,pps,start code.
  编码音频数据 提供原始数据类型与编码后数据类型的ASBD 指定编码器类型 kAudioEncoderComponentType  创建编码器 AudioConverterNewSpecific  设置编码器属性: 比特率, 编码质量等 将1024个采样点原始PCM数据传入编码器 开始编码:  AudioConverterFillComplexBuffer  获取编码后的AAC数据
  音视频同步
  以编码的第一帧视频的系统时间作为音视频数据的基准时间戳,随后将采集到音视频数据中的时间戳减去该基准时间戳作为各自的时间戳, 同步有两种策略,一种是以音频时间戳为准, 即当出现错误时,让视频时间戳去追音频时间戳,这样做即会造成看到画面会快进或快退,二是以视频时间戳为准,即当出现错误时,让音频时间戳去追视时间戳,即声音可能会刺耳,不推荐.所以一般使用第一种方案,通过估计下一帧视频时间戳看看如果超出同步范围则进行同步.
  FFmpeg合成数据流 初始化FFmpeg相关参数: AVFormatContext (管理合成上下文), AVOutputFormat(合成流格式), AVStream(音视频数据流)... 创建上下文对象 AVFormatContext  :  avformat_alloc_context  根据数据类型生成编码器 AVCodec  :  avcodec_find_encoder   视频: AV_CODEC_ID_H264/AV_CODEC_ID_HEVC  ,音频: AV_CODEC_ID_AAC  生成流 AVStream  :  avformat_new_stream  指定音视频流中各个参数信息, 如数据格式,视频宽高帧率,比特率,基准时间,extra data, 音频:采样率,声道数, 采样位数等等. 指定上下文及流格式中的音视频编码器id:  video_codec_id, audio_codec_id  生成视频流头数据: 当音视频编码器都填充到上下文对象后,即可生产该类型对应的头信息, 此头信息作为解码音视频数据的重要信息,一定需要正确合成. avformat_write_header  将音视频数据装入动态数组中. 合成音视频数据: 通过另一条线程取出动态数组中的音视频数据,通过比较时间戳的方式进行同步合成. 将音视频数据装入AVPacket中 产生合成的数据 av_write_frame
  C++音视频学习资料免费获取方法:关注音视频开发T哥  ,点击「链接」即可免费获取2023年最新 C++音视频开发进阶独家免费学习大礼包! 3.2 文件结构
  2.file3.3 快速使用初始化相关模块 - (void)viewDidLoad {     [super viewDidLoad];     // Do any additional setup after loading the view.          [self configureCamera];     [self configureAudioCapture];     [self configureAudioEncoder];     [self configurevideoEncoder];     [self configureAVMuxHandler];     [self configureAVRecorder]; }在相机回调中将原始yuv数据送去编码 - (void)xdxCaptureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {     if ([output isKindOfClass:[AVCaptureVideoDataOutput class]] == YES) {         if (self.videoEncoder) {             [self.videoEncoder startEncodeDataWithBuffer:sampleBuffer                                         isNeedFreeBuffer:NO];                      }              } }通过回调函数接收编码后的视频数据并将其送给合成流类. #pragma mark Video Encoder - (void)receiveVideoEncoderData:(XDXVideEncoderDataRef)dataRef {     [self.muxHandler addVideoData:dataRef->data size:(int)dataRef->size timestamp:dataRef->timestamp isKeyFrame:dataRef->isKeyFrame isExtraData:dataRef->isExtraData videoFormat:XDXMuxVideoFormatH264]; }在采集音频回调中接收音频数据并编码,最终将编码数据也送入合成流类 #pragma mark Audio Capture and Audio Encode - (void)receiveAudioDataByDevice:(XDXCaptureAudioDataRef)audioDataRef {     [self.audioEncoder encodeAudioWithSourceBuffer:audioDataRef->data                                   sourceBufferSize:audioDataRef->size                                                pts:audioDataRef->pts                                    completeHandler:^(XDXAudioEncderDataRef dataRef) {                                        if (dataRef->size > 10) {                                            [self.muxHandler addAudioData:(uint8_t *)dataRef->data                                                                     size:dataRef->size                                                               channelNum:1                                                               sampleRate:44100                                                                timestamp:dataRef->pts];                                                                                   }                                        free(dataRef->data);                                    }]; }先写文件后,随后接收合成后的数据并写入文件. #pragma mark Mux - (IBAction)startRecordBtnDidClicked:(id)sender {     int size = 0;     char *data = (char *)[self.muxHandler getAVStreamHeadWithSize:&size];     [self.recorder startRecordWithIsHead:YES data:data size:size];     self.isRecording = YES; }   - (void)receiveAVStreamWithIsHead:(BOOL)isHead data:(uint8_t *)data size:(int)size {     if (isHead) {         return;     }          if (self.isRecording) {         [self.recorder startRecordWithIsHead:NO data:(char *)data size:size];     } }4、具体实现
  本例中音视频采集编码模块在前面文章中已经详细介绍,这里不再重复,如需帮助请参考上文的阅读前提.下面仅介绍合成流. 4.1 初始化FFmpeg相关对象.AVFormatContext: 管理合成流上下文对象 AVOutputFormat: 合成流的格式,这里使用的asf数据流 AVStream: 音视频数据流具体信息 - (void)configureFFmpegWithFormat:(const char *)format {     if(m_outputContext != NULL) {         av_free(m_outputContext);         m_outputContext = NULL;     }          m_outputContext = avformat_alloc_context();     m_outputFormat  = av_guess_format(format, NULL, NULL);          m_outputContext->oformat    = m_outputFormat;     m_outputFormat->audio_codec = AV_CODEC_ID_NONE;     m_outputFormat->video_codec = AV_CODEC_ID_NONE;     m_outputContext->nb_streams = 0;          m_video_stream     = avformat_new_stream(m_outputContext, NULL);     m_video_stream->id = 0;     m_audio_stream     = avformat_new_stream(m_outputContext, NULL);     m_audio_stream->id = 1;          log4cplus_info(kModuleName, "configure ffmpeg finish."); }4.2 配置视频流的详细信息
  设置该编码的视频流中详细的信息, 如编码器类型,配置信息,原始视频数据格式,视频的宽高,比特率,帧率,基准时间戳,extra data等.
  这里最重要的就是extra data,注意,因为我们要根据extra data才能生成正确的头数据,而asf流需要的是annux b格式的数据,苹果采集的视频数据格式为avcc所以在编码模块中已经将其转为annux b格式的数据,并通过参数传入,这里可以直接使用,关于这两种格式区别也可以参考阅读前提中的码流介绍的文章. - (void)configureVideoStreamWithVideoFormat:(XDXMuxVideoFormat)videoFormat extraData:(uint8_t *)extraData extraDataSize:(int)extraDataSize {     if (m_outputContext == NULL) {         log4cplus_error(kModuleName, "%s: m_outputContext is null",__func__);         return;     }          if(m_outputFormat == NULL){         log4cplus_error(kModuleName, "%s: m_outputFormat is null",__func__);         return;     }      AVFormatContext *formatContext = avformat_alloc_context();     AVStream *stream = NULL;     if(XDXMuxVideoFormatH264 == videoFormat) {         AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);         stream = avformat_new_stream(formatContext, codec);         stream->codecpar->codec_id = AV_CODEC_ID_H264;     }else if(XDXMuxVideoFormatH265 == videoFormat) {         AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_HEVC);         stream = avformat_new_stream(formatContext, codec);         stream->codecpar->codec_tag      = MKTAG("h", "e", "v", "c");         stream->codecpar->profile        = FF_PROFILE_HEVC_MAIN;         stream->codecpar->format         = AV_PIX_FMT_YUV420P;         stream->codecpar->codec_id       = AV_CODEC_ID_HEVC;     }          stream->codecpar->format             = AV_PIX_FMT_YUVJ420P;     stream->codecpar->codec_type         = AVMEDIA_TYPE_VIDEO;     stream->codecpar->width              = 1280;     stream->codecpar->height             = 720;     stream->codecpar->bit_rate           = 1024*1024;     stream->time_base.den                = 1000;     stream->time_base.num                = 1;     stream->time_base                    = (AVRational){1, 1000};     stream->codec->flags                |= AV_CODEC_FLAG_GLOBAL_HEADER;          memcpy(m_video_stream, stream, sizeof(AVStream));          if(extraData) {         int newExtraDataSize = extraDataSize + AV_INPUT_BUFFER_PADDING_SIZE;         m_video_stream->codecpar->extradata_size = extraDataSize;         m_video_stream->codecpar->extradata      = (uint8_t *)av_mallocz(newExtraDataSize);         memcpy(m_video_stream->codecpar->extradata, extraData, extraDataSize);     }          av_free(stream);      m_outputContext->video_codec_id = m_video_stream->codecpar->codec_id;     m_outputFormat->video_codec     = m_video_stream->codecpar->codec_id;          self.isReadyForVideo = YES;          [self productStreamHead]; }4.3 配置音频流的详细信息
  首先根据编码音频的类型生成编码器并生成流对象,然后 配置音频流的详细信息,如压缩数据格式,采样率,声道数,比特率,extra data等等.这里要注意的是extra data是为了保存mp4文件时播放器能够正确解码播放准备的,可以参考这几篇文章:audio extra data1,audio extra data2 - (void)configureAudioStreamWithChannelNum:(int)channelNum sampleRate:(int)sampleRate {     AVFormatContext *formatContext  = avformat_alloc_context();     AVCodec         *codec          = avcodec_find_encoder(AV_CODEC_ID_AAC);     AVStream        *stream         = avformat_new_stream(formatContext, codec);          stream->index         = 1;     stream->id            = 1;     stream->duration      = 0;     stream->time_base.num = 1;     stream->time_base.den = 1000;     stream->start_time    = 0;     stream->priv_data     = NULL;          stream->codecpar->codec_type     = AVMEDIA_TYPE_AUDIO;     stream->codecpar->codec_id       = AV_CODEC_ID_AAC;     stream->codecpar->format         = AV_SAMPLE_FMT_S16;     stream->codecpar->sample_rate    = sampleRate;     stream->codecpar->channels       = channelNum;     stream->codecpar->bit_rate       = 0;     stream->codecpar->extradata_size = 2;     stream->codecpar->extradata      = (uint8_t *)malloc(2);     stream->time_base.den  = 25;     stream->time_base.num  = 1;          /*      * why we put extra data here for audio: when save to MP4 file, the player can not decode it correctly      * http://ffmpeg-users.933282.n4.nabble.com/AAC-decoder-td1013071.html      * http://ffmpeg.org/doxygen/trunk/mpeg4audio_8c.html#aa654ec3126f37f3b8faceae3b92df50e      * extra data have 16 bits:      * Audio object type - normally 5 bits, but 11 bits if AOT_ESCAPE      * Sampling index - 4 bits      * if (Sampling index == 15)      * Sample rate - 24 bits      * Channel configuration - 4 bits      * last reserved- 3 bits      * for exmpale:  "Low Complexity Sampling frequency 44100Hz, 1 channel mono":      * AOT_LC == 2 -> 00010      -              * 44.1kHz == 4 -> 0100      +              * 44.1kHz == 4 -> 0100  48kHz == 3 -> 0011      * mono == 1 -> 0001      * so extra data: 00010 0100 0001 000 ->0x12 0x8      +                  00010 0011 0001 000 ->0x11 0x88      +      */          if (stream->codecpar->sample_rate == 44100) {         stream->codecpar->extradata[0] = 0x12;         //iRig mic HD have two chanel 0x11         if(channelNum == 1)             stream->codecpar->extradata[1] = 0x8;         else             stream->codecpar->extradata[1] = 0x10;     }else if (stream->codecpar->sample_rate == 48000) {         stream->codecpar->extradata[0] = 0x11;         //iRig mic HD have two chanel 0x11         if(channelNum == 1)             stream->codecpar->extradata[1] = 0x88;         else             stream->codecpar->extradata[1] = 0x90;     }else if (stream->codecpar->sample_rate == 32000){         stream->codecpar->extradata[0] = 0x12;         if (channelNum == 1)             stream->codecpar->extradata[1] = 0x88;         else             stream->codecpar->extradata[1] = 0x90;     }     else if (stream->codecpar->sample_rate == 16000){         stream->codecpar->extradata[0] = 0x14;         if (channelNum == 1)             stream->codecpar->extradata[1] = 0x8;         else             stream->codecpar->extradata[1] = 0x10;     }else if(stream->codecpar->sample_rate == 8000){         stream->codecpar->extradata[0] = 0x15;         if (channelNum == 1)             stream->codecpar->extradata[1] = 0x88;         else             stream->codecpar->extradata[1] = 0x90;     }          stream->codec->flags|= AV_CODEC_FLAG_GLOBAL_HEADER;          memcpy(m_audio_stream, stream, sizeof(AVStream));          av_free(stream);          m_outputContext->audio_codec_id = stream->codecpar->codec_id;     m_outputFormat->audio_codec     = stream->codecpar->codec_id;          self.isReadyForAudio = YES;      [self productStreamHead]; }4.4 生成流头数据
  当前面2,3部都配置完成后,我们将音视频流注入上下文对象及对象中的流格式中,即可开始生成头数据. avformat_write_header  - (void)productStreamHead {     log4cplus_debug("record", "%s,line:%d",__func__,__LINE__);          if (m_outputFormat->video_codec == AV_CODEC_ID_NONE) {         log4cplus_error(kModuleName, "%s: video codec is NULL.",__func__);         return;     }          if(m_outputFormat->audio_codec == AV_CODEC_ID_NONE) {         log4cplus_error(kModuleName, "%s: audio codec is NULL.",__func__);         return;     }          /* prepare header and save header data in a stream */     if (avio_open_dyn_buf(&m_outputContext->pb) < 0) {         avio_close_dyn_buf(m_outputContext->pb, NULL);         log4cplus_error(kModuleName, "%s: AVFormat_HTTP_FF_OPEN_DYURL_ERROR.",__func__);         return;     }              /*      * HACK to avoid mpeg ps muxer to spit many underflow errors      * Default value from FFmpeg      * Try to set it use configuration option      */     m_outputContext->max_delay = (int)(0.7*AV_TIME_BASE);              int result = avformat_write_header(m_outputContext,NULL);     if (result < 0) {         log4cplus_error(kModuleName, "%s: Error writing output header, res:%d",__func__,result);         return;     }              uint8_t * output = NULL;     int len = avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output));     if(len > 0 && output != NULL) {         av_free(output);                  self.isReadyForHead = YES;                  if (m_avhead_data) {             free(m_avhead_data);         }         m_avhead_data_size = len;         m_avhead_data = (uint8_t *)malloc(len);         memcpy(m_avhead_data, output, len);                  if ([self.delegate respondsToSelector:@selector(receiveAVStreamWithIsHead:data:size:)]) {             [self.delegate receiveAVStreamWithIsHead:YES data:output size:len];         }                  log4cplus_error(kModuleName, "%s: create head length = %d",__func__, len);     }else{         self.isReadyForHead = NO;         log4cplus_error(kModuleName, "%s: product stream header failed.",__func__);     } }4.5 然后将传来的音视频数据装入数组中
  该数组通过封装C++中的vector实现一个轻量级数据结构以缓存数据. 4.6 合成音视频数据
  新建一条线程专门合成音视频数据,合成策略即取出音视频数据中时间戳较小的一帧先写,因为音视频数据总体偏差不大,所以理想情况应该是取一帧视频,一帧音频,当然因为音频采样较快,可能会相对多一两帧,而当音视频数据由于某种原因不同步时,则会等待,直至时间戳重新同步才能继续进行合成.     int err = pthread_create(&m_muxThread,NULL,MuxAVPacket,(__bridge_retained void *)self);     if(err != 0){         log4cplus_error(kModuleName, "%s: create thread failed: %s",__func__, strerror(err));     }          void * MuxAVPacket(void *arg) {     pthread_setname_np("XDX_MUX_THREAD");     XDXAVStreamMuxHandler *instance = (__bridge_transfer XDXAVStreamMuxHandler *)arg;     if(instance != nil) {         [instance dispatchAVData];     }          return NULL; }  #pragma mark Mux - (void)dispatchAVData {     XDXMuxMediaList audioPack;     XDXMuxMediaList videoPack;          memset(&audioPack, 0, sizeof(XDXMuxMediaList));     memset(&videoPack, 0, sizeof(XDXMuxMediaList));          [m_AudioListPack reset];     [m_VideoListPack reset];      while (true) {         int videoCount = [m_VideoListPack count];         int audioCount = [m_AudioListPack count];         if(videoCount == 0 || audioCount == 0) {             usleep(5*1000);             log4cplus_debug(kModuleName, "%s: Mux dispatch list: v:%d, a:%d",__func__,videoCount, audioCount);             continue;         }                  if(audioPack.timeStamp == 0) {             [m_AudioListPack popData:&audioPack];         }                  if(videoPack.timeStamp == 0) {             [m_VideoListPack popData:&videoPack];         }                  if(audioPack.timeStamp >= videoPack.timeStamp) {             log4cplus_debug(kModuleName, "%s: Mux dispatch input video time stamp = %llu",__func__,videoPack.timeStamp);                          if(videoPack.data != NULL && videoPack.data->data != NULL){                 [self addVideoPacket:videoPack.data                            timestamp:videoPack.timeStamp                  extraDataHasChanged:videoPack.extraDataHasChanged];                                  av_free(videoPack.data->data);                 av_free(videoPack.data);             }else{                 log4cplus_error(kModuleName, "%s: Mux Video AVPacket data abnormal",__func__);             }             videoPack.timeStamp = 0;         }else {             log4cplus_debug(kModuleName, "%s: Mux dispatch input audio time stamp = %llu",__func__,audioPack.timeStamp);                          if(audioPack.data != NULL && audioPack.data->data != NULL) {                 [self addAudioPacket:audioPack.data                            timestamp:audioPack.timeStamp];                 av_free(audioPack.data->data);                 av_free(audioPack.data);             }else {                 log4cplus_error(kModuleName, "%s: Mux audio AVPacket data abnormal",__func__);             }                          audioPack.timeStamp = 0;         }     } }4.7 获取合成好的视频流
  通过 av_write_frame  即可获取合成好的数据. - (void)productAVDataPacket:(AVPacket *)packet extraDataHasChanged:(BOOL)extraDataHasChanged {     BOOL    isVideoIFrame = NO;     uint8_t *output       = NULL;     int     len           = 0;          if (avio_open_dyn_buf(&m_outputContext->pb) < 0) {         return;     }          if(packet->stream_index == 0 && packet->flags != 0) {         isVideoIFrame = YES;     }          if (av_write_frame(m_outputContext, packet) < 0) {         avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output));         if(output != NULL)             free(output);                  log4cplus_error(kModuleName, "%s: Error writing output data",__func__);         return;     }               len = avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output));          if(len == 0 || output == NULL) {         log4cplus_debug(kModuleName, "%s: mux len:%d or data abnormal",__func__,len);         if(output != NULL)             av_free(output);         return;     }              if ([self.delegate respondsToSelector:@selector(receiveAVStreamWithIsHead:data:size:)]) {         [self.delegate receiveAVStreamWithIsHead:NO data:output size:len];     }          if(output != NULL)         av_free(output); }
  原文链接:iOS瀹屾暣鎺ㄦ祦閲囬泦闊宠棰戞暟鎹紪鐮佸悓姝ュ悎鎴愭祦 - 绠€涔�

退休金每月12000元,属于什么水平?看到这个退休金每月12000多元的数字,真想找块布遮住自己的头,普通工人要干三个月,农民要种两年的田地,到现在才明白,为什么物价那么高,为什么农民宁愿丟荒土地也不想种,不说大家也明工作了一个多月没有签合同,现在要辞职,领导要我签了合同然后在三天后办离职手续,这合理吗?又一个被劳务派遣坑骗的人。我的建议是拨打劳动仲裁部门的电话,让劳动仲裁部门出面去协调这个事情,你的单位涉嫌克扣员工的利益,虽然不是很严重,但是这个事情绝对不会只发生在你一个人的身上有些人宁可在国企里拿着几千工资混饭吃,也不愿辞职创业,好吗?你说有些人宁愿拿着几千元的工资混饭吃,也不愿意辞职创业,这好吗?我媳妇就在国企工作,我身边有许多朋友也是国企职工,所以很愿意就这个问题谈谈我的看法。国企有国企的优越性,虽然工资不是乡镇公务员一边是提副科,一边有机会调到市直单位,该如何选择?正常情况下,毫无疑问应该选择市直单位!为什么这么说呢?我一个中学同学和一位大学同学,中学同学大学毕业之后考入了某乡镇公务员,而大学同学先是当兵2年,然后考入了重庆市某部门单位公务员有人知道超市的供货模式吗?哈哈,我回来回答一下,本人是业务员一名,专门负责给超市供货,目前在超市快消行业已经做了三年!我来说说超市的供货模式吧!!本文以面积500平方以上的超市来举例!一般新开超市,在装修期你们见过什么奇葩的人?我们公司以前有一个女同事,来公司上班的时候孩子大概1周岁。工作了半年不到,她提出离职,原因是离婚了,要带孩子回娘家去住,娘家在外地,不得已才离职。公司老板听说这件事之后觉得她挺可怜你听过的最意外的死亡是什么?说说我亲身经历的事情吧,是我的小姑父。我小姑比我只大5岁,小的时候也经常一起玩,小姑当年考上了天津的一所大学,在那里她结识了一个兵哥哥,他们谈起了恋爱,兵哥哥河南的,长得蛮帅气的,监狱里的犯人会不会互殴?监狱里的犯人会互殴吗?答案是肯定的!当年,我年轻气盛,就因为聚众斗殴被关了一年多,往事不堪回首!刚进去的第一天,是在拘留所,人只有失去自由,才知道自由的可贵!一个十来平方的小屋子,挂职与任职的区别是什么?我曾经在区县组织部门做过多年干部工作,我相信我的回答是比较靠谱的。第一,我先用比较容易理解的语言来解释一下两者的概念。关于挂职,是指通过组织程序,有计划的选派在职干部,在一定时间内广西省来宾市的发展前景怎么样,该如何发展?来宾市是广西壮族自治区的一座著名城市。在此需要纠正一下这个题目,广西不是省。我国一级地方行政区划分别是中央直辖市省民族自治区特别行政区共计四类。其中广西是全国五个民族自治区之一,中老年人怎么存钱好?老年人怎么存钱好,对于上年纪的老年人来说,个人并不建议选择一些风险比较高的理财产品,因为家庭基本上均以稳定,儿女都已经成家立业家中大小事以及各类生活支出,可以说均是由儿女们来承担,
秋燥最怕这菜,三天两头吃一次,润肺防秋燥,不受秋燥影响导读每年一进入到秋天,能明显地感觉到空气变得越来越干燥,此时的饮食,应以养阴清热润燥止渴清心安神的食物为主,才能使我们更健康的度过整个秋天。而老话说荷莲一身宝,秋藕最补人,这是因为秋天,红薯山药不如它,高钾高钙,多买些一晒一腌,常吃对脾胃好导语山药芋头靠边站,脾胃不好多吃它,富含维C,高钾高钙,别不懂吃!俗语说秋天多吃瓜,好处一箩筐。秋天是由热到冷过渡的季节,由于降水比较少,天气也比较干燥。这个时候,多吃一些富含维生双标的韩系车!中美区别对待,四代同堂卖,活该卖不好进入中国市场20年,北京现代陷入了20而惑,韩系车在华的市场占有率,已经跌到了2。而在6年前,韩系车在华市场还是处于销量巅峰,那一年北京现代销售114万辆,国内排名第6名。2016TEGIC桌面充电站可分离模块,充电速度飙升150W,再快就不礼貌了前言现如今的快节奏生活中,唯有自己的桌面才是打工人下班后唯一休憩的港湾而打造专属于自己的桌面,简约不冗杂才是重点。2022年4月5日,TEGIC推出了新一代高性能组合式桌面充电站QMagicLeap2头显海外发布,约23522元起IT之家10月1日消息,MagicLeap2头显于9月30日发售,起价为3299美元(约23522元人民币)。这款混合现实设备是2018年MagicLeap的继任者,它比其前身轻约三星为GalaxyS21Ultra系列国行版推送安卓13OneUI5。0Beta更新目前。三星S21S21S21Ultra已经可以体验到最新的操作系统了。正在使用上述机型的用户可以通过使用OneUIBeta程序,来体验三星专门为GalaxyS21S21S21Ult湖北阿姨50年前7块钱买的一瓶酒,现在卖翻了好几倍,这是为何?好像近些年我们看到会比较少了,但是在前些时候经常能够在路上看到那种回收酒的。酒和别的东西都不一样,别的东西都是有一个时间期限,但是酒并不是,或者可以说他和别的东西好像是正好相反一样42年前,中国印度GDP持平,如今中国世界第二,印度多久能追上?2022年二季度,印度经济迎来了爆发式增长,GDP涨幅同比高达13。5,这也让印度人信心爆棚,自信要在短期内追赶日本,印度有何底气?42年前,中印GDP持平2021年印度GDP为3大话西游2经典版亲密孝心400的孩子,需要做多久心性修炼长安城站着一位灵钧先生,通过此人能给孩子增加亲密和孝心。点击灵钧先生对话,选择挑战云麓(获得心性),领取任务。然后点击任务栏云麓中的幻城,自动寻路到女儿国(240,103)位置,进初秋衣服不用多买,有这2裤4裙就够了,显瘦又好看初秋想要穿的优雅又高级根本没那么复杂,有这2裤4裙就够了!不仅可以在视觉上最大程度的穿出显瘦显高的效果,而且搭配各种风格的上衣都很时髦好看,各种身材都能穿!今天就手把手教大家如何将61年前,美国把一只黑猩猩送进太空,回来后却发现了不对劲如果您喜欢这篇作品,欢迎点击右上方关注。感谢您的鼓励与支持,希望能给您带来舒适的阅读体验。提起宇航员这个身份,大家脑海中首先想到的肯定都是人,但在61年前,美国却训练出了一只黑猩猩