精华内容
下载资源
问答
  • 参考资料,主要实现包括利用librtmp实现rtmp的接收,并保存成flv格式视频;利用ffmpeg接口实现rtmp保存成.ts, .mkv, .flv, .mp4格式视频; mp4v2源码及封装
  • 目前android上,录相大多是mp4的视频,这在一般情况下,已经...比如突然撞车了,或者是远程监控断电了,如果这时录的是Mp4的视频,那么就会导致,没有来得及写和mp4的文件头信息,而打不开视频。所以在远程监控录相...

           目前android上,录相大多是mp4的视频,这在一般情况下,已经够用了。但是在一些特定的场景,比如远程临控录相或者行车记录仪上,用mp4录相,就不太理想了。为什么呢?因为远程录相,或者行车记录仪上都有一个共同的问题,那就是录相有可能中断。比如突然撞车了,或者是远程监控断电了,如果这时录的是Mp4的视频,那么就会导致,因为没有来得及写mp4的文件头信息,从而打不开视频。所以在远程监控录相和行车记录仪上,录相的格式,最好使用mpeg2ts流。

            现在android无论是8.0还是9.0、10.0上,都支持录制mpeg2ts流视频,但是却不支持用MediaMuxer的writeSampleData去打包mpeg2ts。这就导致了一个问题,比如有的app上,想将一个mp4的视频,转成mpeg2ts流的视频,就无法在java端完成。且现在android8.1(9.0、10.0上没有试),录下来的mpeg2ts流,经常会丢帧,最后几帧录不下来。这个Mpeg2Ts功能显得很鸡肋。下面,我们就来讨论一下,怎么去解决这些问题。

            先说一下mpeg2ts录相丢帧的问题。mpeg2ts录相的framework层cpp文件是frameworks\av\media\libstagefrightMPEG2TSWriter.cpp这一个。写数据的函数是:MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) 这个函数:

    void MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            case kWhatSourceNotify:
            {
                int32_t sourceIndex;
                CHECK(msg->findInt32("source-index", &sourceIndex));
                sp<SourceInfo> source = mSources.editItemAt(sourceIndex);
    
                int32_t what;
                CHECK(msg->findInt32("what", &what));
    
                if (what == SourceInfo::kNotifyReachedEOS
                        || what == SourceInfo::kNotifyStartFailed) {
                    source->setEOSReceived();
    
                    sp<ABuffer> buffer = source->lastAccessUnit();
                    source->setLastAccessUnit(NULL);
    
                    if (buffer != NULL) {
                        writeTS();
                        writeAccessUnit(sourceIndex, buffer);
                    }
    
                    ++mNumSourcesDone;
                } else if (what == SourceInfo::kNotifyBuffer) {
                    sp<ABuffer> buffer;
                    CHECK(msg->findBuffer("buffer", &buffer));
                    CHECK(source->lastAccessUnit() == NULL);
    
                    int32_t oob;
                    if (msg->findInt32("oob", &oob) && oob) {
                        // This is codec specific data delivered out of band.
                        // It can be written out immediately.
                        writeTS();
                        writeAccessUnit(sourceIndex, buffer);
                        break;
                    }
    
                    // We don't just write out data as we receive it from
                    // the various sources. That would essentially write them
                    // out in random order (as the thread scheduler determines
                    // how the messages are dispatched).
                    // Instead we gather an access unit for all tracks and
                    // write out the one with the smallest timestamp, then
                    // request more data for the written out track.
                    // Rinse, repeat.
                    // If we don't have data on any track we don't write
                    // anything just yet.
                    source->setLastAccessUnit(buffer);
    
    
                    
    
                    ALOGV("lastAccessUnitTimeUs[%d] = %.2f secs",
                        sourceIndex, source->lastAccessUnitTimeUs() / 1E6);
                    int64_t minTimeUs = -1;
                    size_t minIndex = 0;
    
                    for (size_t i = 0; i < mSources.size(); ++i) {
                        const sp<SourceInfo> &source = mSources.editItemAt(i);
    
                        if (source->eosReceived()) {
                            continue;
                        }
    
                        int64_t timeUs = source->lastAccessUnitTimeUs();
                        if (timeUs < 0) {
                            minTimeUs = -1;
                            break;
                        } else if (minTimeUs < 0 || timeUs < minTimeUs) {
                            minTimeUs = timeUs;
                            minIndex = i;
                        }
                    }
    
                    if (minTimeUs < 0) {
                        ALOGV("not all tracks have valid data.");
                        break;
                    }
    
                    ALOGV("writing access unit at time %.2f secs (index %zu)",
                        minTimeUs / 1E6, minIndex);
    
                    source = mSources.editItemAt(minIndex);
    
    
                    
                    buffer = source->lastAccessUnit();
                    source->setLastAccessUnit(NULL);
    
                    writeTS();
                    writeAccessUnit(minIndex, buffer);
    
                    source->readMore();
                }
                break;
            }
    
            default:
                TRESPASS();
        }
    }

            在这个函数里,收到数据,并写入到文件的是“what == SourceInfo::kNotifyBuffer”这个条件下的代码段。注意在这个代码段里的那个for循环,我们丢帧就是在这里丢的。 

            这个for循环的作用是干什么呢?它的作用是,选取当前录制的视频的几个源中,时间戳最小的那一个源的数据,并将选取的源的数据写入文件。这么做的原因上面的注释写了,大意是,一个视频会有几个源,分属不同的线程。因为在不同的线程,所以调度时间有先后顺序,有时视频数据已经读取到了,但是cpu现在调度的是音频源,视频数据就要等音频源写完数据后,再去写视频源的数据,这样就会导致声音和视频有错位。比如播放的时候,声音说完了,对应的画面过了一秒才播出来。

            google的这个解释,似乎说的通,似乎有那么一丝的道理。但是实际上,这段代码逻辑却是有混乱不堪。再举个例子,比如当前收到的是视频帧,视频帧的timeUS,也就是时间戳是112233。然后第一次执行完这个for循环后,minTimeUs会等于112233,i=1。然后因为还有音频,会第二次执行这个for循环。假设这时音频的时间戳是112232,它比视频的时间戳小,那么,minTimeUS就被改成了112232, minIndex=2。好了,执行完上面两次for循环后,会马上执行source = mSources.editItemAt(minIndex);,去取出音频的数据,写入文件,然后再紧接着调用source->readMore();去继续读取音频的内容。

            不知道大家有没有注意到,本来这次发送SourceInfo::kNotifyBuffer这个整个的源是视频源,但是到最后,写入的数据却是音频源的。那么视频源的数据到哪里去了呢?没错,居然被直接丢弃掉了,丢弃掉了........

            不知道写这段逻辑的人的脑子是怎么长的,总之,这里的逻辑是个很明显的错误。想要解决这个问题也很简单,把这个fro循环去掉,SourceInfo::kNotifyBuffer这个源是谁发过来的,就写谁的数据,不用去管时间戳。因为mpeg2ts流数据,每个pes数据包中,都包含了它的时间戳。具体的可以看下面的代码:

    void MPEG2TSWriter::writeAccessUnit(
            int32_t sourceIndex, const sp<ABuffer> &accessUnit) {
        ........
        int64_t timeUs;
        CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
    
        uint32_t PTS = (timeUs * 9ll) / 100ll;
        ........
    }

             再者,在视频文件里,无论是哪种格式的,音频轨和视频轨都是分开存放的。接收存储数据时,只用管当前轨道的数据是按先后顺序存放的就可以。 所以,根本不需要多此一举,在收到某个源的数据后,还要和其他源的数据比时间戳。修改后的代码如下:

    void MPEG2TSWriter::onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            case kWhatSourceNotify:
            {
                int32_t sourceIndex;
                CHECK(msg->findInt32("source-index", &sourceIndex));
                sp<SourceInfo> source = mSources.editItemAt(sourceIndex);
    
                int32_t what;
                CHECK(msg->findInt32("what", &what));
    
                if (what == SourceInfo::kNotifyReachedEOS
                        || what == SourceInfo::kNotifyStartFailed) {
                    source->setEOSReceived();
    
                    sp<ABuffer> buffer = source->lastAccessUnit();
                    source->setLastAccessUnit(NULL);
    
                    if (buffer != NULL) {
                        writeTS();
                        writeAccessUnit(sourceIndex, buffer);
                    }
    
                    ++mNumSourcesDone;
                } else if (what == SourceInfo::kNotifyBuffer) {
                    sp<ABuffer> buffer;
                    CHECK(msg->findBuffer("buffer", &buffer));
                    CHECK(source->lastAccessUnit() == NULL);
    
                    int32_t oob;
                    if (msg->findInt32("oob", &oob) && oob) {
                        // This is codec specific data delivered out of band.
                        // It can be written out immediately.
                        writeTS();
                        writeAccessUnit(sourceIndex, buffer);
                        break;
                    }
    
                    // We don't just write out data as we receive it from
                    // the various sources. That would essentially write them
                    // out in random order (as the thread scheduler determines
                    // how the messages are dispatched).
                    // Instead we gather an access unit for all tracks and
                    // write out the one with the smallest timestamp, then
                    // request more data for the written out track.
                    // Rinse, repeat.
                    // If we don't have data on any track we don't write
                    // anything just yet.
                    source->setLastAccessUnit(buffer);
    #if 0                
                    ALOGV("lastAccessUnitTimeUs[%d] = %.2f secs",
                        sourceIndex, source->lastAccessUnitTimeUs() / 1E6);
                    int64_t minTimeUs = -1;
                    size_t minIndex = 0;
    
                    for (size_t i = 0; i < mSources.size(); ++i) {
                        const sp<SourceInfo> &source = mSources.editItemAt(i);
    
                        if (source->eosReceived()) {
                            continue;
                        }
    
                        int64_t timeUs = source->lastAccessUnitTimeUs();
                        if (timeUs < 0) {
                            minTimeUs = -1;
                            break;
                        } else if (minTimeUs < 0 || timeUs < minTimeUs) {
                            minTimeUs = timeUs;
                            minIndex = i;
                        }
                    }
    
                    if (minTimeUs < 0) {
                        ALOGV("not all tracks have valid data.");
                        break;
                    }
    
                    ALOGV("writing access unit at time %.2f secs (index %zu)",
                        minTimeUs / 1E6, minIndex);
    
                    source = mSources.editItemAt(minIndex);
    #endif
    
                    
                    buffer = source->lastAccessUnit();
                    source->setLastAccessUnit(NULL);
    
                    writeTS();
                    //writeAccessUnit(minIndex, buffer);
                    writeAccessUnit(sourceIndex, buffer);  
    
                    source->readMore();
                }
                break;
            }
    
            default:
                TRESPASS();
        }
    }

            好了,上面这样修改后,经过反复测试验证,录下来的视频不存在丢帧的问题,丢帧的问题完美的解决了。

            现在再来说说,怎么去提供mpeg2ts流的mediamuxer给java层使用。先上一段java上的测试代码:

    	    MediaExtractor extractor;
    		int trackCount;
    		MediaMuxer muxer;	
    		HashMap<Integer, Integer> indexMap;
    
    		private void cloneMediaUsingMuxer(FileDescriptor srcMedia, String dstMediaPath,
    										  int expectedTrackCount, int degrees, int fmt) throws IOException {
    			// Set up MediaExtractor to read from the source.
    			extractor = new MediaExtractor();
    			extractor.setDataSource(srcMedia, 0, testFileLength);
    	
    			trackCount = extractor.getTrackCount();
    			muxer = new MediaMuxer(dstMediaPath, fmt);
    	
    			 indexMap = new HashMap<Integer, Integer>(trackCount);
    			for (int i = 0; i < trackCount; i++) {
    				extractor.selectTrack(i);
    				MediaFormat format = extractor.getTrackFormat(i);
    				int dstIndex = muxer.addTrack(format);
    				indexMap.put(i, dstIndex);
    			}
    	
    			if (degrees >= 0) {
    				muxer.setOrientationHint(degrees);
    			}
    			muxer.start();
    
    
    		    Handler handler = new Handler();
    			   handler.postDelayed(new Runnable() {
    				   @Override
    				   public void run() {
    						// Copy the samples from MediaExtractor to MediaMuxer.
    						boolean sawEOS = false;
    						int bufferSize = MAX_SAMPLE_SIZE;
    						int frameCount = 0;
    						int offset = 100;
    				
    						ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
    						BufferInfo bufferInfo = new BufferInfo();			
    						
    					   while (!sawEOS) {
    						   bufferInfo.offset = offset;
    						   bufferInfo.size = extractor.readSampleData(dstBuf, offset);
    						   if (bufferInfo.size < 0) {
    							   sawEOS = true;
    							   bufferInfo.size = 0;
    						   } else {
    							   bufferInfo.presentationTimeUs = extractor.getSampleTime();
    							   bufferInfo.flags = extractor.getSampleFlags();
    							   int trackIndex = extractor.getSampleTrackIndex();
    							   muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
    									   bufferInfo);
    							   extractor.advance();
    							   frameCount++;
    						   }
    					   }
    					   
    					   muxer.stop();
    					   muxer.release();
    
    				   }
                   //这里延时10毫秒执行,是因为mpeg2ts的muxer有时启动稍慢。如果writeSampleData的
                   //的时候,muxer还没启动,就会报错
    			   }, 10);
    			return;
    		}

            这段代码中,就做了一件事,那就是从给定的文件里,用MediaExtractor去抽出每一帧,然后再用MediaMuxer将抽出的帧,打包成指定格式的视频文件。我们的目的是将一个给定的视频,通过mediaMuxer打包成mpeg2ts流视频。但是从frameworks\base\media\java\android\media\MediaMuxer.java的setUpMediaMuxer里可以看出,目前android不支持转成mpeg2ts流。要想达到我们的目的,首先需要在setUpMediaMuxer这个函数里,将mpeg2ts格式给加上去。

        public static final class OutputFormat {
            /* Do not change these values without updating their counterparts
             * in include/media/stagefright/MediaMuxer.h!
             */
            private OutputFormat() {}
            /** MPEG4 media file format*/
            public static final int MUXER_OUTPUT_MPEG_4 = 0;
            /** WEBM media file format*/
            public static final int MUXER_OUTPUT_WEBM   = 1;
            /** 3GPP media file format*/
            public static final int MUXER_OUTPUT_3GPP   = 2;
    		
    		public static final int MUXER_OUTPUT_MPEG2TS   = 3;
        };
    
    
        private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
            if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM
                    && format != OutputFormat.MUXER_OUTPUT_3GPP && format != OutputFormat.MUXER_OUTPUT_MPEG2TS) {
                throw new IllegalArgumentException("format: " + format + " is invalid");
            }
            mNativeObject = nativeSetup(fd, format);
            mState = MUXER_STATE_INITIALIZED;
            mCloseGuard.open("release");
        }
    

           在上面,我们新增了一个格式MUXER_OUTPUT_MPEG2TS 。然后这里就一步步的调到了frameworks\av\media\libstagefright\MediaMuxer.cpp,同样,我们需要在这个文件里,增加我们的格式:

        enum OutputFormat {
            OUTPUT_FORMAT_MPEG_4      = 0,
            OUTPUT_FORMAT_WEBM        = 1,
            OUTPUT_FORMAT_THREE_GPP   = 2,    
            //add by mpeg2ts
            OUTPUT_FORMAT_YUNOVO_MPEG2TS     = 3,
            OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
        };
    
    MediaMuxer::MediaMuxer(int fd, OutputFormat format)
        : mFormat(format),
          mState(UNINITIALIZED) {
        ALOGV("MediaMuxer start, format=%d", format); 
        if (format == OUTPUT_FORMAT_MPEG_4 || format == OUTPUT_FORMAT_THREE_GPP) {
            mWriter = new MPEG4Writer(fd);
        } else if (format == OUTPUT_FORMAT_WEBM) {
            mWriter = new WebmWriter(fd);
        }
        //add mpeg2ts
        else if (format == OUTPUT_FORMAT_YUNOVO_MPEG2TS){
            mWriter = new MPEG2TSWriter(fd);
        }//add end
    
        if (mWriter != NULL) {
            mFileMeta = new MetaData;
            mState = INITIALIZED;
        }
    }

            好了,到这里为止,从java到c++层的接口,就算是打通了。现在就可以使用extractor.readSampleData去抽取视频帧数据,然后使用muxer.writeSampleData去写mpeg2ts流文件了。

            

            下面顺便说一下,这个抽帧和写帧的流程。我们在MediaMuxer.cpp里构建好Muxer后,就可以在java层上通过muxer.addTrack(format),将源文件里的视频track和音频track甚至字幕track添加进来了。

    ssize_t MediaMuxer::addTrack(const sp<AMessage> &format) {
        Mutex::Autolock autoLock(mMuxerLock);
        if (format.get() == NULL) {
            ALOGE("addTrack() get a null format");
            return -EINVAL;
        }
    
        if (mState != INITIALIZED) {
            ALOGE("addTrack() must be called after constructor and before start().");
            return INVALID_OPERATION;
        }
    
        sp<MetaData> trackMeta = new MetaData;
        convertMessageToMetaData(format, trackMeta);
    
        sp<MediaAdapter> newTrack = new MediaAdapter(trackMeta);
        status_t result = mWriter->addSource(newTrack); 
        if (result == OK) { 
            return mTrackList.add(newTrack); 
        }
        return -1;
    }

            我们注意到,这里的track,是一个MediaAdapter类。请大家记住这个类,因为后面我们在java层调用writeSampleData去写帧数据时,最终都是通过这个类去push buffer的。

    status_t MediaMuxer::writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,
                                         int64_t timeUs, uint32_t flags) {
        Mutex::Autolock autoLock(mMuxerLock);
        ALOGV("MediaMuxer::writeSampleData trackIndex= %zu; timeUs= %" PRIu64, trackIndex, timeUs);
        if (buffer.get() == NULL) {
            ALOGE("WriteSampleData() get an NULL buffer.");
            return -EINVAL;
        }
    
        if (mState != STARTED) {
            ALOGE("WriteSampleData() is called in invalid state %d", mState);
            return INVALID_OPERATION;
        }
    
        if (trackIndex >= mTrackList.size()) {
            ALOGE("WriteSampleData() get an invalid index %zu", trackIndex);
            return -EINVAL;
        }
        ALOGV("MediaMuxer::writeSampleData buffer offset = %zu, length = %zu", buffer->offset(), buffer->size());
        MediaBuffer* mediaBuffer = new MediaBuffer(buffer);
    
        mediaBuffer->add_ref(); // Released in MediaAdapter::signalBufferReturned().
        mediaBuffer->set_range(buffer->offset(), buffer->size());
    
        sp<MetaData> sampleMetaData = mediaBuffer->meta_data();
        sampleMetaData->setInt64(kKeyTime, timeUs);
        // Just set the kKeyDecodingTime as the presentation time for now.
        sampleMetaData->setInt64(kKeyDecodingTime, timeUs);
    
        if (flags & MediaCodec::BUFFER_FLAG_SYNCFRAME) {
            sampleMetaData->setInt32(kKeyIsSyncFrame, true);
        }
    
        sp<MediaAdapter> currentTrack = mTrackList[trackIndex];
        // This pushBuffer will wait until the mediaBuffer is consumed.
        return currentTrack->pushBuffer(mediaBuffer);
    }

            每写一帧时,都会在mediaMuxer.cpp里,调用MediaAdapter的接口,去pushBuffer。这个pushBuffer,将数据push到哪里去了,可以跟到frameworks\av\media\libstagefright\MediaAdapter.cpp里来看看:

    void MediaAdapter::signalBufferReturned(MediaBuffer *buffer) {
        Mutex::Autolock autoLock(mAdapterLock);
        CHECK(buffer != NULL);
        buffer->setObserver(0);
        buffer->release();
        ALOGV("buffer returned %p", buffer);
        mBufferReturnedCond.signal();
    }
    
    status_t MediaAdapter::read(
                MediaBuffer **buffer, const ReadOptions * /* options */) {
        Mutex::Autolock autoLock(mAdapterLock);
        if (!mStarted) {
            ALOGV("Read before even started!");
            return ERROR_END_OF_STREAM;
        }
    
        while (mCurrentMediaBuffer == NULL && mStarted) {
            ALOGV("waiting @ read()");
            mBufferReadCond.wait(mAdapterLock);
        }
    
        if (!mStarted) {
            ALOGV("read interrupted after stop");
            CHECK(mCurrentMediaBuffer == NULL);
            return ERROR_END_OF_STREAM;
        }
    
        CHECK(mCurrentMediaBuffer != NULL);
    
        *buffer = mCurrentMediaBuffer;
        mCurrentMediaBuffer = NULL;
        (*buffer)->setObserver(this);
    
        return OK;
    }
    
    status_t MediaAdapter::pushBuffer(MediaBuffer *buffer) {
        if (buffer == NULL) {
            ALOGE("pushBuffer get an NULL buffer");
            return -EINVAL;
        }
    
        Mutex::Autolock autoLock(mAdapterLock);
        if (!mStarted) {
            ALOGE("pushBuffer called before start");
            return INVALID_OPERATION;
        }
        mCurrentMediaBuffer = buffer;
        mBufferReadCond.signal();
    
        ALOGV("wait for the buffer returned @ pushBuffer! %p", buffer);
        mBufferReturnedCond.wait(mAdapterLock);
    
        return OK;
    }

            从pushBuffer函数里可以看到,每当mCurrentMediaBuffer = buffer;这样赋值后,就会通过mBufferReadCond.signal();发送信号。这个mBufferReadCond的接收者在read函数里。当read收到消息后,就会将值通过read的指针传送到调用read的地方。调用read的地方是frameworks\av\media\libstagefright\MPEG2TSWriter.cpp里的下面的函数:

    void MPEG2TSWriter::SourceInfo::onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            ......
    
            case kWhatRead:
            {
                MediaBuffer *buffer;
                status_t err = mSource->read(&buffer);
    
                if (err != OK && err != INFO_FORMAT_CHANGED) {
                    sp<AMessage> notify = mNotify->dup();
                    notify->setInt32("what", kNotifyReachedEOS);
                    notify->setInt32("status", err);
                    notify->post();
                    break;
                }
    
                if (err == OK) {
                    if (mStreamType == 0x0f && mAACCodecSpecificData == NULL) {
                        // The first audio buffer must contain CSD if not received yet.
                        CHECK_GE(buffer->range_length(), 2u);
                        mAACCodecSpecificData = new ABuffer(buffer->range_length());
    
                        memcpy(mAACCodecSpecificData->data(),
                               (const uint8_t *)buffer->data()
                                + buffer->range_offset(),
                               buffer->range_length());
                        readMore();
                    } else if (buffer->range_length() > 0) {
                        if (mStreamType == 0x0f) {
                            appendAACFrames(buffer);
                        } else {
                            appendAVCFrame(buffer);
                        }
                    } else {
                        readMore();
                    }
    
                    buffer->release();
                    buffer = NULL;
                }
    
                // Do not read more data until told to.
                break;
            }
    
            default:
                TRESPASS();
        }
    }

            这里在读到数据后,通过判断是音频的还是视频的,丢给不同的函数去处理。比如是视频的话,就会丢给appendAVCFrame去处理。

    void MPEG2TSWriter::SourceInfo::appendAVCFrame(MediaBuffer *buffer) {
        sp<AMessage> notify = mNotify->dup();
        notify->setInt32("what", kNotifyBuffer);
    
        if (mBuffer == NULL || buffer->range_length() > mBuffer->capacity()) {
            mBuffer = new ABuffer(buffer->range_length());
        }
        mBuffer->setRange(0, 0);
    
        memcpy(mBuffer->data(),
               (const uint8_t *)buffer->data()
                + buffer->range_offset(),
               buffer->range_length());
    
        int64_t timeUs;
        CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
        mBuffer->meta()->setInt64("timeUs", timeUs);
    
        int32_t isSync;
        if (buffer->meta_data()->findInt32(kKeyIsSyncFrame, &isSync)
                && isSync != 0) {
            mBuffer->meta()->setInt32("isSync", true);
        }
    
        mBuffer->setRange(0, buffer->range_length());
    
        notify->setBuffer("buffer", mBuffer);
        notify->post();
    }

            从这个函数里我们可以看到,appendAVCFrame函数,只对数据帧设置时间戳和同步标志后,就通过一个通知,丢给了MPEG2TSWriter::onMessageReceived去处理。MPEG2TSWriter::onMessageReceived收到帧后的处理过程,就是最开始咱们讨论的那个地方了。

            另外,如果我们是从指定的mpeg2ts流文件里抽帧,然后再通过mpeg2tswriter去打包成一个新的ts流的话,有一个地方需要注意。那就是MPEG2TSWriter::SourceInfo::appendAACFrames(MediaBuffer *buffer)这个函数里的开始的地方,加个判断:

        if(mIsMuxer)
        {
            buffer->set_range(7, buffer->range_length()-7);
        }
    

           因为这里加个属性来判断,当是在muxer时,就要加上下面这一行.因为现有的ts视频,每一帧音频已经加上了
           7个字节的音频头.如果不将这7个字节的音频头给去掉,会导致每一帧音频上又多加了一个7字节的音频头.
           这样的后果会导致大部份的播放器识别不了这个音频,播放不出了声音.

            到此为止,我们就将mpeg2ts的流程梳理完成了,并且修正了录相丢帧的bug,封装了mpeg2ts muxer java层接口。

    展开全文
  • 此文章的方法试用于各大直播平台点播平台。HLS协议是什么?HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的媒体网络传输协议。​是苹果公司QuickTime XiPhone软件系统的一部分。 它的工作...

    3a719c52a9187a58222c10d43049c12e.png
    声明:此文中用的软件均为开源软件,完全免费。
    此文章的方法试用于各大直播平台和点播平台。

    HLS协议是什么?

    HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。​是苹果公司QuickTime X和iPhone软件系统的一部分。 它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。

    TS视频流就是HLS协议中的一个几秒钟的片段,M3U8文件是这些片段的一个目录一个索引文件。弄清楚这些就好理解接下来为啥要合并TS视频了。

    工具

    需要用到的软件:M3U8-Downloader

    推荐理由:开源、免费、跨多个平台、功能完备,下载稳定。

    Windows系统支持
    Mac系统支持
    Linux系统支持
    HLS协议点播源 ✓
    自定义Http协议头下载 ✓
    自定义KEY和IV解密 ✓
    本地M3U8文件下载 ✓
    HLS协议直播源 ✓
    标准 AES-128-CBC加密 ✓

    Windows、Mac、Linux下载文件:releases

    3d85cf8db9cc27ac84d8d106fa63dbf1.gif
    视频下载缓存

    M3U8下载教程

    抓取视频流:

    在chrome浏览器打开视频网页,按下F12,页签点击到Network页面,在Filter框里输入”m3u8”,然后按F5刷新页面,如果网页里的视频使用的是HLS源,就可以在这里捕获到视频流地址,然后选中右键 Copy -> Copy Link Address.

    视频教程:

    知乎视频www.zhihu.com

    TS文件合并教程

    直接使用M3U8-Downloader使用里面的合并视频功能,导入选择ts视频流,点击开始合并按钮,就可以看到合成的视频了。

    此款软件有两种合并模式:
    1.快速合并就是软件只解封装不解码视频把所有视频片段重新封装成MP4文件。
    2.修复合并,是在有一些文件损坏的时候使用的,这种模式可以对所有视频片段重新编码,并封装成MP4文件。

    68f2eb7fc0fd84d66f268cce5c153c1d.gif
    合并示例教程

    ---- 完 ----

    展开全文
  • 在过去,TS格式是流行的视频封装格式,可以包含多个音轨字幕等数据。但随着时代的发展,TS格式已被渐渐淘汰,在使用时可能会遇到播放支持的问题。MP4视频格式作为现在适用性与普遍性最高的视频格式,将TS视频格式...

    文章来源:https://www.reneelab.com.cn/convert-ts-to-mp4-format.html

    一、什么是TS视频文件

    TS(Transport Stream)是使用标准MEPG-2(.MPEG)视频压缩压缩视频数据的视频文件,通常用于DVB和ATSC广播系统,以及用于在DVD上存储视频内容。TS文件格式可以同时存储多个数据,比如音频、视频数据。MPEG2-TS格式的特点就是从视频流的任一片段开始都是可以独立解码的。

    TS格式是R系列编码标准的专用封装格式,这系列的编码标准至今都没有开源的编码器。同时随着H.264/AVC编解码器标准的推广,MP4格式也逐渐被人们所熟知。MP4格式具有编码效率高、通用性、容错性强等特点,渐渐地MP4格式已取代了过去流行的TS格式。

    存储在DVD上的TS视频文件可以在DVD播放器中直接打开播放,而无需额外的软件。如果您的电脑有TS视频文件,您可以尝试双击打开它。不同的操作系统可能会出现不同的结果,例如Mac系统的电脑就需要额外的软件才能播放,否则无法打开。

    遇到无法打开的问题,小编建议您将TS视频格式进行转换为较为通用的MP4格式,该格式可在多种不同类型的设备中播放。

    二、TS视频文件转为MP4格式的方法

    在将TS视频转换为MP4格式之前,部分视频是可以选择音轨和字幕。TS格式的视频文件在过去的译制片电影中是比较流行的,因TS文件可以包含多个音轨和字幕。传入中国的译制片,都会对电影进行中文配音与字幕翻译,所以当我们在网上下载电影时可能会有多种字幕、多音轨的情况。

    如果您想要中文配音、简体字幕的效果,那么使用都叫兽™视频编辑软件就可以帮您快速转换需要的字幕或配音,并保存为MP4格式。

    点击此处下载

    ① 首先,在您的电脑上下载并安装都叫兽™ 视频编辑软件。打开软件,选择“视频编辑工具”进入。
    视频编辑工具

    ② 进入视频编辑页面,点击“添加文件”,上传视频文件。
    添加视频文件

    ③ 您可选择需要的音轨(英文配音或中文配音)和字幕类型。如下图所示:
    选择视频音轨

    ④ 在输出格式栏选择MP4视频格式;在输出文件夹栏中选择视频要保存的位置。最后点击“开始”按钮即可变换成功并保存。
    ts视频转MP4

    展开全文
  • 为了接收多播,您需要创建一个多播客户端,该客户端具有用于存储视频...使用ffmpeg开始将mp4视频文件流式传输到多播地址端口 .ffmpeg -i .\mars.mp4 -c:v libx264 -c:a libmp3lame -f mpegts udp://239.1.1.1:...

    为了接收多播流,您需要创建一个多播客户端,该客户端具有用于存储视频数据的缓冲区,并使用可以加入和侦听多播流的套接字 .

    这两个属性是多播地址(239.1.1.1)和端口(49410) .

    使用ffmpeg开始将mp4视频文件流式传输到多播地址和端口 .

    ffmpeg -i .\mars.mp4 -c:v libx264 -c:a libmp3lame -f mpegts udp://239.1.1.1:49410

    编译并运行使用MulticastSocket类的多类客户端加入组播组并侦听UDP流数据包 . 我们将缓冲区传递给DatagramPacket对象,当套接字收到UDP数据包时,缓冲区将填充mpeg-ts数据 . 然后,您可以将缓冲区复制到应用程序的另一部分以解码数据 .

    import java.io.IOException;

    import java.net.DatagramPacket;

    import java.net.InetAddress;

    import java.net.MulticastSocket;

    import java.net.UnknownHostException;

    public class Client {

    final static String INET_ADDR = "239.1.1.1";

    final static int PORT = 49410;

    public static void main(String[] args) throws UnknownHostException {

    // Get the multicast address that we are going to connect to.

    InetAddress address = InetAddress.getByName(INET_ADDR);

    // Create a buffer of bytes, which will be used to store

    // the incoming bytes containing the information from the streaming server

    byte[] buffer = new byte[256];

    // Create a new Multicast socket so we can join the multicast group

    try (MulticastSocket clientSocket = new MulticastSocket(PORT)){

    //Joint the Multicast group.

    clientSocket.joinGroup(address);

    // do an infinite loop

    while (true) {

    // Receive the information and print it.

    DatagramPacket msgPacket = new DatagramPacket(buffer, buffer.length);

    clientSocket.receive(msgPacket);

    String data = new String(buffer, 0, buffer.length);

    System.out.println("Data -> " + data);

    }

    } catch (IOException exception) {

    exception.printStackTrace();

    }

    }

    }

    展开全文
  • ffmpeg转mp4ts 命令行实现

    千次阅读 2015-07-15 16:30:03
    在使用hls技术播放视频时,首先要把视频转换为ts一个m3u8播放列表,使用ffmpeg进行该转换时(低版本ffmpeg不支持直接转,只能现在转换成ts,再用m3u8-segmenter切片,笔者使用的是ffmpeg version-2.1.2),通常...
  • FFMPEG_FLV_MP4_TS_MUX_本地文件_2,支持本地文件 ,和流传输demo
  • 在使用hls技术播放视频时,首先要把视频转换为ts一个m3u8播放列表,使用ffmpeg进行该转换时(低版本ffmpeg不支持直接转,只能现在转换成ts,再用m3u8-segmenter切片,笔者使用的是ffmpeg version-2.1.2),通常...
  • 对于TS文件,相信很多人都对它很陌生,它并不像MP4格式那么流行。为了让大家进一步了解TS文件,本文将围绕TS文件格式,详细介绍它的来源、适用范围、打开方式以及如何编辑转换等。
  • Mp4box的下载、安装使用

    千次阅读 2016-05-31 17:19:04
    它可以被用来对诸如AVI, MPG, TS, 但更多地是对于ISO媒体文件(如MP4, 3GP)进行操作。 简单地说MP4Box可以被用来:  对诸如MP4, 3GP之类的ISO文件进行操作,如:添加、移除、混  执行的加密操作  将元数据附着...
  • TsMp4优势对比Mp4在IOS下可以自动播放,但是在部分安卓机下无法自动播放产生黑屏。Ts可实现自动播放,IOS8以上Android4.4以上都支持。基于自动播放的优势,本妹子在本厂的618大促主会场及各个活动需求上就用了Ts...
  • 很强大的Directshow分离器,如果你的系统中某些文件不能播放,试试这个,配合FFDshow Decode ...Haali Media Splitter is a DirectShow splitter for .mkv (Matroska), .mp4, .ogg/.ogm, .avi and MPEG TS handling.
  • 因此,首先需要把mp4格式的文件转成ts格式,不过要注意的是,我们要拼接mp4文件,而不是被转码的mp4文件,一次这里转换只需要改变封装格式,音频流和视频流直接复制就行。转换方法:ffmpeg-i1.mp4-vcodeccopy-vbsfh...
  • ffmpeg: 录屏

    2020-05-25 11:56:26
    录屏 ffmpeg -video_size 1920...ffmpeg -re -i RBD-725.mp4 -vcodec copy -f mpegts udp://127.0.0.1:1234 ffplay -protocol_whitelist "file,udp,rtp" -i udp://192.168.2.102:1234 RTP ffmpeg -re -i RBD-725.m
  • Golang音频/视频库和流服务器 JOY4是用golang编写的功能强大的库,经过精心设计的界面使几行代码可以完成很多工作,例如在各种媒体格式之间进行读取,写入,转码或设置高性能实时服务器。 特征 设计良好且易于...
  • MPEG4与.mp4

    2017-06-29 18:34:03
    媒体应用中TS和MP4格式分析应该是封包格式。不能简单理解成MPEG4的简称。要详细解释这个问题,需要提一下MPEG4和.mp4在概念上的区别。 一般来说,仅提“MPEG4”,是指一种视频压缩算法。可以把原始画面通过数学...
  • DVD转MP4转换器

    2017-03-12 13:47:24
    随着DVD播放机在家庭中出现的越来越少,不少朋友需要把获得的DVD光盘中的视频转换成MP4视频格式,这样能更加方便的传输观看。DVD转MP4转换器就是一款非常易用的DVD转MP4转换工具。 DVD转MP4转换器可以在不变化...
  • 因此,首先需要把mp4格式的文件转成ts格式,不过要注意的是,我们要拼接mp4文件,而不是被转码的mp4文件,一次这里转换只需要改变封装格式,音频流和视频流直接复制就行。转换方法:ffmpeg-i1.mp4-vcodeccopy-vbsfh...
  • MP4,RMDSActice转换专家

    2018-05-30 15:55:57
    MP4/RM转换专家支持将几乎所有视频格式比如:RM/RMVB/VOB/AVI/MPEG/DAT/VCD/SVCD/DVD/ASF/WMV/MOV/QT/MP4/3GP/3GPP2/FLV/F4V/MKV/TS/TP/MTS/M2TS/DV/YUV等视频文件以最快速度转换为普通MP4机、SONY PSV/PSP、PS3、...
  • RMVB转MP4转换器 v3.2.rar

    2019-07-08 12:51:37
    它不仅支持将常见的RM/RMVB文件转换成MP4机、手机、iPad、iPhone、PSP等支持MP4格式,而且还支持将AVI、VCD、SVCD、VOB、MPEG、DAT、WMV、ASF、MOV、QT、MKV、FLV、MP4、3GP、DV、MTS、TS、M2TS、MOD、TOD、F4V等...
  • FFmpeg提取es

    千次阅读 2018-06-18 21:15:21
    Annex B格式通常应用于网络流播放,常见如ts流, AVCC格式通常应用于本地硬盘播放,常见如mp4、mkv等封装格式下es流为了实现nalu的分割需要包含nalu的前缀0x000001或者0x00000001。因此AVCC下需要转换成包括前缀的...
  • 比如您可以将流行的视频格式AVI, RMVB, WMV, MPG, TS, 3GP, 3G2, VOB, ASF, MKV, SWF, FLV, DV, DPG, AMV, MTV转换为MP4格式视频。这样您就可以在苹果的iPad、iPhone、iPod、索尼PSP、各种安卓智能手机、高清液晶...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 196
精华内容 78
热门标签
关键字:

ts流和mp4